热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

erlang_mysql_driver源码分析4

其实本来是要讲erlang如何解决tcp粘包问题的,刚好erlang_mysql_driver里面就有关于这个问题的一种解决方式,所以干脆就以erlan

其实本来是要讲erlang如何解决tcp粘包问题的,刚好erlang_mysql_driver里面就有关于这个问题的一种解决方式,所以干脆就以erlang_mysql_driver的源码为例来探究下该问题的解决方案。

tcp 粘包问题

mysql的协议包是建立在tcp的基础上的,而tcp协议是流协议,也就是在使用的时候可以保证按顺序收到,但是并不是对方发送多少次,我们就能接收多少次。
这就是tcp粘包问题了,例如你调用两次gen_tcp:send,发了两句话,“你好”“吃饭了吗”。可是我这边在调用gen_tcp:recv接收的时候,可能是需要调用3次,分别收到 “你”“好吃”“饭了吗”。那我就根本不知道你在说什么了。
针对上述问题的解决方案是在协议自定义的时候用len+body的形式,len所占的空间固定,比如只占一个字节,那么你就可以这样发“2你好4吃饭了吗”。我这边每次先取一个字节,得到2,然后就取后面2个数据“你好”。再次循环,取一个字节4,然后取后面4个字节“吃饭了吗”。这样我们就可以正常聊天了。(当然中文不止一个字节,我随便定的,不要纠结哈)本文要讲的就是针对这种方案的erlang具体设计了。
另外关于tcp粘包问题的概念也挺多的,有一种特别的看法是把粘包问题看成传输层的问题,然后得出的结论是tcp以前会出现“粘包问题”现在不会了。这种概念的“粘包问题”是指多进程通过同一端口发送消息如何避免乱序。这里不会对这些进行讨论,在这篇文章中我理解的tcp粘包问题是在应用层的,也就是因为tcp的字节流无边界,发送方发出N个协议包,接收方收到M个协议包的问题,N可能大于M也可能小于M。



mysql_recv是怎么处理tcp粘包

erlang_mysql_driver使用mysql_recv模块来接收mysql服务器发送来的数据,那么mysql_recv进程肯定是需要处理tcp粘包的。

mysql_recv建立连接的方式

我们先看下,mysql_recv是怎么建立连接的?
这个连接建立的过程在mysql_recv:init中

init(Host, Port, LogFun, Parent) ->case gen_tcp:connect(Host, Port, [binary, {packet, 0}, {keepalive, true}]) of{ok, Sock} ->Parent ! {mysql_recv, self(), init, {ok, Sock}},State &#61; #state{socket &#61; Sock,parent &#61; Parent,log_fun &#61; LogFun,data &#61; <<>>},loop(State);E ->LogFun(?MODULE, ?LINE, error,fun() ->{"mysql_recv: Failed connecting to ~p:~p : ~p",[Host, Port, E]}end),Msg &#61; lists:flatten(io_lib:format("connect failed : ~p", [E])),Parent ! {mysql_recv, self(), init, {error, Msg}}end.

gen_tcp:connect(Host, Port, [binary, {packet, 0}, {keepalive, true}])
这里有个很重要的默认参数就是{active, true}&#xff0c;在不显式指定active的时候&#xff0c;默认为true。
true指定的是主动消息接收&#xff0c;也就是一旦有数据达到这个Socket&#xff0c;那么这个Socket的控制进程&#xff08;mysql_recv&#xff09;就会收到{tcp, …}, {tcp_error, …}, {tcp_closed,…}这一类的消息。

而packet这个参数&#xff0c;跟我们要讨论的tcp粘包问题关系就很大了&#xff0c;它就是erlang用来解决tcp粘包的&#xff0c;如{packet, 1}表明协议包的第一个bytes用来表示长度&#xff0c;那么我们只需要直接接收数据就行了&#xff0c;或者直接发送数据。erlang会自动解析协议包头的长度&#xff0c;但是在mysql_recv中并没有使用packet这个功能。



mysql_recv send_packet

那么mysql_recv自己是怎么解决的呢&#xff1f;我们看它收到数据后怎么做的&#xff1f;

loop(State) ->Sock &#61; State#state.socket,receive{tcp, Sock, InData} ->NewData &#61; list_to_binary([State#state.data, InData]),%% send data to parent if we have enough dataRest &#61; sendpacket(State#state.parent, NewData),loop(State#state{data &#61; Rest});{tcp_error, Sock, Reason} ->LogFun &#61; State#state.log_fun,LogFun(?MODULE, ?LINE, error,fun() ->{"mysql_recv: Socket ~p closed : ~p",[Sock, Reason]}end),State#state.parent ! {mysql_recv, self(), closed, {error, Reason}},error;{tcp_closed, Sock} ->LogFun &#61; State#state.log_fun,LogFun(?MODULE, ?LINE, debug,fun() ->{"mysql_recv: Socket ~p closed", [Sock]}end),State#state.parent ! {mysql_recv, self(), closed, normal},errorend.

每次收到数据后InData后&#xff0c;先把之前缓存在mysql_recv state中的data取出&#xff0c;然后和InData合并。NewData &#61; InData &#43; 之前缓存的Data&#xff0c;然后调用send_packet。

%% send data to parent if we have enough data
sendpacket(Parent, Data) ->case Data of<<Length:24/little, Num:8, D/binary>> ->ifLength &#61;<size(D) ->{Packet, Rest} &#61; split_binary(D, Length),Parent ! {mysql_recv, self(), data, Packet, Num},sendpacket(Parent, Rest);true ->Dataend;_ ->Dataend.

sendpacket并没有直接发送数据&#xff0c;sendpacket取出前面3个字节&#xff0c;作为这个包长度的值。为什么是3个字节呢&#xff1f;为什么不是2个或4个呢&#xff0c;因为这是mysql规定&#xff0c;mysql的数据包就是用前面3个字节表示长度&#xff0c;第4个字节表示序号&#xff0c;可见上面的代码匹配 Length:24, Num:8。
当我们知道这个包的长度后&#xff0c;就看后面的data够不够了&#xff0c;如果不够那就继续进入#state.data中缓存。如果够了&#xff0c;那就按Length截取出数据&#xff0c;发送给mysql_conn&#xff0c;剩下的data继续进入#state.data中缓存。



其他处理tcp粘包的方法

事实上erlang本身的{packet, N}这个参数就提供了&#xff0c;强大的tcp粘包处理。
在不考虑{packet&#xff0c; N}的情况下&#xff0c;其他处理tcp粘包的方法&#xff1a;

  • mysql_recv给我们提供了一种主动接收信息{active, true}时处理粘包的方式&#xff0c;这种方式其实就是准备了一个数据缓存区&#xff08;#state.data&#xff09;&#xff0c;然后按包头定义的长度取数据&#xff0c;多的放回&#xff0c;少的继续等待。
  • 使用参数{active,false}的时候&#xff0c;我们是用gen_tcp:recv(Socket, Len)的方式来接收数据的&#xff0c;只有调用了recv的时候才会去按Len指定的长度获取数据&#xff0c;那么这种方式的话&#xff0c;其实解决的方法也比较简单。每次先gen_tcp:recv(Socket, 3)&#xff0c;接收到数据后取出该数据的大小DataLen&#xff0c;然后调用gen_tcp:recv(Socket, DataLen).这次接收完数据后&#xff0c;继续gen_tcp:recv(Socket, 3)就行了。

当然上面说的两种方式&#xff0c;包括{packet, N}都是建立在我们自定义的协议包使用固定的字节数&#xff08;如mysql的3&#xff09;&#xff0c;来表示该报的长度。包的格式 &#61; Len &#43; Body&#xff0c;Len所占的字节数固定。这里的Len的字节数固定后&#xff0c;这个包的最大值其实也就知道了&#xff0c;如mysql的一个包最大为16m&#xff08;(2^24)-1&#61;(16M-1)字节&#xff09;&#xff0c;超过该值就要分包发送。


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • WhenIusepythontoapplythepymysqlmoduletoaddafieldtoatableinthemysqldatabase,itdo ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • 修复安装win10失败并提示“磁盘布局不受UEFI固件支持”的方法
    本文介绍了修复安装win10失败并提示“磁盘布局不受UEFI固件支持”的方法。首先解释了UEFI的概念和作用,然后提供了两种解决方法。第一种方法是在bios界面中将Boot Mode设置为Legacy Support,Boot Priority设置为Legacy First,并关闭UEFI。第二种方法是使用U盘启动盘进入PE系统,运行磁盘分区工具DiskGenius,将硬盘的分区表设置为gpt格式,并留出288MB的内存。最后,通过运行界面输入命令cmd来完成设置。 ... [详细]
  • Oracle Database 10g许可授予信息及高级功能详解
    本文介绍了Oracle Database 10g许可授予信息及其中的高级功能,包括数据库优化数据包、SQL访问指导、SQL优化指导、SQL优化集和重组对象。同时提供了详细说明,指导用户在Oracle Database 10g中如何使用这些功能。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • 本文详细介绍了MySQL表分区的创建、增加和删除方法,包括查看分区数据量和全库数据量的方法。欢迎大家阅读并给予点评。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
author-avatar
mobiledu2502879833
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有