ejabberd的网络数据粘包处理

:: 编程, Erlang, 代码分析, ejabberd

By: David Gao

对ejabberd的源代码分析,ejabberd是如何处理网络数据的粘包情况。

什么是粘包

因为UDP的协议特性,粘包现象并不会出现在UDP当中。因此粘包一般都出现在TCP当中,不 过并不是使用TCP进行数据传输就会产生粘包。

TCP长链接

客户端和服务器建立起一个TCP连接后,进行多次数据交换而不断开,必要的时候会插入和 数据无关的心跳包来保持TCP连接不被中间路由强制断开。

TCP短链接

客户端与服务器没进行一次数据交换就建立一次连接,完成数据交换后立刻断开连接。

TCP粘包的产生

从上面可以看出,只有是TCP长连接存在的时候,才会发生粘包,因为TCP是流式传输的。在 应用层面上虽然可以划分出一个个数据包,但是TCP传输层会将数据包整合一起发送或缓存 着被应用一次性取出。简单的说就是:

  1. 发送方需要等到缓存区被填充满了之后,再发送,因此造成了粘包现象。
  2. 接收方不能快速的处理缓存区中的包,造成多个包堆积在缓存区中,因此造成了粘包现 象。

如何解决TCP粘包

最常见的方式就是,在每次发送数据包之前,先定义一个数据包长度。在接收数据包的时候 先读取数据包长度,再读取整个数据包。进一步去完成业务操作。

ejabberd是如何处理粘包问题

作为即时通讯,ejabberd在和普通的客户端进行通讯的时候底层技术也使用的是TCP长连接 (Web做客户端还有websocket和BOSH这两个技术)。因此ejabberd也会出现粘包这一现象, 这里就介绍下ejabberd是如何处理粘包的。

ejabberd_receiver模块

ejabberd_receiver可以说是eJabberd的XML输入流最主要的部分,它负责管理了socket,输 入流的流控和XML的解析器。在每次收到数据后,ejabberd_receiver都会更新流量控制器, 防止一个客户端过快的发送数据,而给ejabberd_c2s造成过大的负担。

在ejabberd_receiver进程被创建之后,会立刻初始化一个exml_stream的解析器。因为XMPP 是基于TCP长连接的XML流,而XMPP又规定了XML流上传输的数据是通过XML节这种结构化格式 封装的,所以这里XML的节就相当于数据包前方的长度字段了。简单的说:读取了一个完整 的XML节,就是一个数据包,剩下的数据就是后续的数据包。

exml项目

exml_event

exml_event是一个对libexpat进行封装的NIF,可以进行高效率的XML解析。exml_event会将 xml数据解析成xml_element_start,xml_element_end和xml_cdata这三个标签。如果传入的 数据没有被完全被解析,会保存在libexpat的parser的上下文当中。

exml_stream

exml_stream会将数据交给exml_event进行解析,会对解析出来的events(上面提到的 xml_element_start,xml_element_end和xml_cdata)进行转换。将它们转化成 xmlstreamstart,xmlstreamend和xmlel的Erlang的record。在exml_stream的上下文当中, 会将没有闭合的xml节保存起来,直到整个XML节闭合后才会交还给ejabberd_receiver。

总结

ejabberd使用XML节闭合的特性,作为数据包的边界来解决粘包问题。同时使用libexpat的 parser来缓存没有解析完的原始数据,使用exml项目中的exml_stream来缓存解析成功,但 是没有闭合的XML节。从而保证了XMPP流中的XMPP节的完整性,以此来解决粘包问题。