`
cwqcwk1
  • 浏览: 89272 次
文章分类
社区版块
存档分类
最新评论

erlang 解决socket 数据粘包问题

 
阅读更多

我们知道,erlang实现的网络服务器性能非常高。erlang的高效不在于短短几行代码就能写出一个服务端程序,而在于不用太多代码,也能够写出一个高效的服务端程序。而这一切的背后就是erlang对很多网络操作实现了近乎完美的封装,使得我们受益其中。文章将讨论erlang gen_tcp 数据连包问题及erlang的解决方案。

数据连包问题,这个在client/server的通讯中很常见。就是,当client在极短的时间内发送多个包给server,这时server在接收数据的时候可能发生连包问题,就一次性接收这几个包的数据,导致数据都粘连在一起。

这里先讨论{packet,raw}或者{packet,0}的情况,分别看下{active, Boolean}的两种方式:

gen_tcp对socket数据封包的获取有以下2种方式,

1、{active, false} 方式通过 gen_tcp:recv(Socket, Length) -> {ok, Data} | {error, Reason}来接收。
2、{active, true} 方式以消息形式{tcp, Socket, Data} |{tcp_closed, Socket} 主动投递给线程。

对于第一种方式 gen_tcp:recv/2,3,如果封包的类型是{packet,raw}或者{packet,0},就需要显式的指定长度,否则封包的长度是对端决定的,长度只能设置为0。如果长度Length设置为0,gen_tcp:recv/2,3会取出Socket接收缓冲区所有的数据

对于第二种方式,缓存区有多少数据,都会全部以消息{tcp, Socket, Data}投递给线程。

以上就会导致数据连包问题,那么如何解决呢?

{packet, PacketType}

现在再来看下 {packet, PacketType},erlang的解释如下:

{packet, PacketType}(TCP/IP sockets)
Defines the type of packets to use for a socket. The following values are valid:

raw | 0
No packaging is done.

1 | 2 | 4
Packets consist of a header specifying the number of bytes in the packet, followed by that number of bytes. The length of header can be one, two, or four bytes; containing an unsigned integer in big-endian byte order. Each send operation will generate the header, and the header will be stripped off on each receive operation.
In current implementation the 4-byte header is limited to 2Gb.

asn1 | cdr | sunrm | fcgi |tpkt |line
These packet types only have effect on receiving. When sending a packet, it is the responsibility of the application to supply a correct header. On receiving, however, there will be one message sent to the controlling process for each complete packet received, and, similarly, each call to gen_tcp:recv/2,3 returns one complete packet. The header is not stripped off.

The meanings of the packet types are as follows:
asn1 - ASN.1 BER,
sunrm - Sun's RPC encoding,
cdr - CORBA (GIOP 1.1),
fcgi - Fast CGI,
tpkt - TPKT format [RFC1006],
line - Line mode, a packet is a line terminated with newline, lines longer than the receive buffer are truncated.

http | http_bin
The Hypertext Transfer Protocol. The packets are returned with the format according to HttpPacket described in erlang:decode_packet/3. A socket in passive mode will return {ok, HttpPacket} from gen_tcp:recv while an active socket will send messages like {http, Socket, HttpPacket}.

httph | httph_bin
These two types are often not needed as the socket will automatically switch from http/http_bin to httph/httph_bin internally after the first line has been read. There might be occasions however when they are useful, such as parsing trailers from chunked encoding.

packet大致意义如下:

raw | 0
没有封包,即不管数据包头,而是根据Length参数接收数据。

1 | 2 | 4
表示包头的长度,分别是1,2,4个字节(2,4以大端字节序,无符号表示),当设置了此参数时,接收到数据后将自动剥离对应长度的头部,只保留Body。

asn1 | cdr | sunrm | fcgi |tpkt|line
设置以上参数时,应用程序将保证数据包头部的正确性,但是在gen_tcp:recv/2,3接收到的数据包中并不剥离头部。

http | http_bin
设置以上参数,收到的数据将被erlang:decode_packet/3格式化,在被动模式下将收到{ok, HttpPacket},主动模式下将收到{http, Socket, HttpPacket}.

{packet, N}

也就是说,如果packet属性为1,2,4,可以保证server端一次接收的数据包大小。

下面我们以 {packet, 2} 做讨论。

gen_tcp 通信传输的数据将包含两部分:包头+数据。gen_tcp:send/2发送数据时,erlang会计算要发送数据的大小,把大小信息存放到包头中,然后封包发送出去。

所以在接收数据时,要根据包头信息,判断接收数据大小。使用gen_tcp:recv/2,3接收数据时,erlang会自动处理包头,获取封包数据。

下面写了个例子来说明,保存为tcp_test.erl

-module(tcp_test).
-export([
    start_server/0,
    start_client_unpack/0, start_client_packed/0
    ]).

-define(PORT, 8888).
-define(PORT2, 8889).

start_server()->
	{ok, ListenSocket} = gen_tcp:listen(?PORT, [binary,{active,false}]),
	{ok, ListenSocket2} = gen_tcp:listen(?PORT2, [binary,{active,false},{packet,2}]),
	spawn(fun() -> accept(ListenSocket) end),
	spawn(fun() -> accept(ListenSocket2) end),
	receive
		_ -> ok
	end.

accept(ListenSocket)->
	case gen_tcp:accept(ListenSocket) of
		{ok, Socket} ->
			spawn(fun() -> accept(ListenSocket) end),
			loop(Socket);
		_ ->
			ok
	end.

loop(Socket)->
	case gen_tcp:recv(Socket,0) of
		{ok, Data}->
			io:format("received message ~p~n", [Data]),
			gen_tcp:send(Socket, "receive successful"),
			loop(Socket);
		{error, Reason}->
			io:format("socket error: ~p~n", [Reason])
	end.

start_client_unpack()->
	{ok,Socket} = gen_tcp:connect({127,0,0,1},?PORT,[binary,{active,false}]),
	gen_tcp:send(Socket, "1"),
	gen_tcp:send(Socket, "2"),
	gen_tcp:send(Socket, "3"),
	gen_tcp:send(Socket, "4"),
	gen_tcp:send(Socket, "5"),
	sleep(1000).

start_client_packed()->
	{ok,Socket} = gen_tcp:connect({127,0,0,1},?PORT2,[binary,{active,false},{packet,2}]),
	gen_tcp:send(Socket, "1"),
	gen_tcp:send(Socket, "2"),
	gen_tcp:send(Socket, "3"),
	gen_tcp:send(Socket, "4"),
	gen_tcp:send(Socket, "5"),
	sleep(1000).

sleep(Count) ->
	receive
	after Count ->
		ok
	end.
运行如下:
C:\>erlc tcp_test.erl
C:\>erl -s tcp_test start_server
Eshell V5.10.2  (abort with ^G)

1> tcp_test:start_client_packed().
received message <<"1">>
received message <<"2">>
received message <<"3">>
received message <<"4">>
received message <<"5">>
ok

2> tcp_test:start_client_unpack().
received message <<"12345">>
ok

字节序

字节序分为两类:Big-Endian和Little-Endian,定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
其实还有一种网络字节序,为TCP/IP各层协议定义的字节序,为Big-Endian。

packet包头是以大端字节序(big-endian)表示。如果erlang与其他语言,比如C++,就要注意字节序问题了。如果机器的字节序是小端字节序(little-endian),就要做转换。

{packet, 2} :[L1,L0 | Data]

{packet, 4} :[L3,L2,L1,L0 | Data]

如何判断机器的字节序,以C++为例

BOOL IsBigEndian()  
{  
    int a = 0x1234;  
    char b =  *(char *)&a;  //通过将int强制类型转换成char单字节,通过判断起始存储位置  
    if( b == 0x12)  
    {  
        return TRUE;  
    }  
    return FALSE;  
}
如何转换字节序,以C++为例
// 32位字数据
#define LittletoBig32(A)   ((( (UINT)(A) & 0xff000000) >> 24) | \
	(( (UINT)(A) & 0x00ff0000) >> 8)   | \
	(( (UINT)(A) & 0x0000ff00) << 8)   | \
	(( (UINT)(A) & 0x000000ff) << 24))

// 16位字数据
#define LittletoBig16(A)   (( ((USHORT)(A) & 0xff00) >> 8)    | \
	(( (USHORT)(A) & 0x00ff) << 8))


参考

http://blog.csdn.net/mycwq/article/details/18359007

http://www.erlang.org/doc/man/inet.html#setopts-2

分享到:
评论

相关推荐

    Erlang6大数据存储方式总结

    虽然MySQL不是Erlang原生的存储解决方案,但其强大的SQL查询能力和成熟的社区支持使其在处理复杂查询和大规模数据时表现出色。通过Erlang与MySQL的集成,开发者可以利用两者的优点,构建混合型的存储解决方案。 ...

    高性能集群服务器Erlang解决方案

    - **内置并发**:Erlang通过轻量级进程(每个进程占用的内存很少且创建速度快)实现并发,每个进程间通过消息传递进行通信,避免了共享内存所带来的复杂性和竞争条件问题。 - **集群能力**:Erlang支持集群全连通或...

    Erlang中的socket编程简单例子

    服务器接收到数据后,将二进制数据转换为Erlang项,处理完数据后,将其重新编码为二进制数据,然后发送回客户端。 客户端方面,gen_tcp模块的connect函数用于建立连接到服务器的socket,send函数用于发送数据。这里...

    erlang websocket

    1. **Websocket协议基础**:Websocket协议定义了一种在单个TCP连接上进行全双工通信的协议,解决了HTTP协议下频繁的请求-响应模式带来的性能问题。它允许服务器主动推送数据到客户端,适合实时性要求高的应用,如...

    java php python erlang 千万级内存数据性能比较

    Erlang的字典数据结构通常比其他语言的哈希表更快,因为它优化了并发访问和内存管理。 接下来是Java,它以其跨平台能力和丰富的库而闻名。"java_class_arr_data_test.jar"和"java_string_arr_data_test.jar"可能是...

    erlang趣学指南

    理解如何有效地使用递归来解决问题是学习Erlang的一个关键点。 错误和异常处理也是Erlang编程的一个重要部分。由于Erlang的设计哲学是容错,因此它提供了一套独特的机制来处理程序中可能出现的错误。 Erlang还提供...

    xiandiao_erlang_Erlang课后习题_

    【Erlang编程语言及其应用】 Erlang是一种并发式、函数式的编程语言,由瑞典电信设备制造商Ericsson开发,...通过实际操练这些课后习题,学习者不仅可以深入理解Erlang语言,还能培养出在实际项目中解决问题的能力。

    handler socket erlang client

    handlersocket是基于mysql的nosql解决方案,与普通的nosql方案比较,具有更大的灵活性,可以使用mysql的索引。性能相比于mysql的批量操作方式,具有5倍左右的提升(我测试的,可能是内存设置的不多)。 详细内容见此...

    erlang编程 Introducing Erlang

    **Erlang编程:Introducing Erlang** Erlang是一种函数式编程语言,由爱立信在1986年开发,主要用于构建高可用性、容错性和并发性的分布式系统。"Introducing Erlang"是Simon St. Laurent撰写的一本入门级教程,...

    erlang文献及资料汇总

    erlang文献及资料汇总 入门资料: erlang中文手册(R11B 文档译文,最适合入门) ...erlang VM内部数据共享机制 erlang 消息传递机制 文章地址:http://blog.csdn.net/mycwq/article/details/43115733

    erlang_版本24.3.4.4

    - **模式匹配**:Erlang的模式匹配功能允许在函数定义中使用模式来匹配和解构数据结构,简化了代码编写。 - **OTP(开放电信平台)**:Erlang OTP是一套库和设计原则,提供了构建可靠系统的框架,包括Mnesia数据库...

    erlang使用post方式发送json数据

    学习erlang的时候尝试编写的小例子,使用post方式发送json数据来进行http请求,希望能帮到大家~

    erlang25.0 windows版本

    4. **错误修复**:解决上一版本中的已知问题,提高稳定性。 5. **并发与分布式特性**:Erlang以其强大的并发处理能力著称,新版本可能在进程管理、消息传递等方面有进一步的改进。 6. **编译器升级**:Erlang的BEAM...

    erlang资源

    1. **Erlang语法**:涵盖基本的变量、数据类型(如原子、列表、元组和二进制)、控制结构(如case表达式和if语句)以及函数定义。 2. **函数式编程概念**:Erlang是纯函数式语言,书中可能会介绍函数式编程的基本...

    Erlang B公式计算器

    Erlang B公式是通信网络领域中用于计算呼叫阻塞概率的重要工具,它在电路交换系统,特别是电话交换网络的...通过学习和使用这个计算器,可以更好地应用Erlang B公式解决实际问题,优化网络性能,降低通话阻塞的可能性。

    <27>erlang record

    标题中的“&lt;27&gt;erlang record”可能指的是Erlang编程语言中的Record特性,它是一种数据结构,类似于结构体或者哈希表,用于组织和操作数据。在Erlang中,Record提供了一种方便的方式来定义和访问具有固定字段的数据...

    erlang programming

    1. **Erlang语言基础**:Erlang是瑞典电信设备制造商Ericsson为解决实时通信系统需求而开发的。它采用函数式编程范式,强调纯函数和不可变数据,以及模式匹配和递归等特性。编程+Erlang.pdf可能会详细介绍Erlang的...

    Erlang官网下载过慢

    Erlang是一种面向并发的、函数式编程语言,主要用于构建高度可...RabbitMQ作为基于Erlang的消息队列,为异步通信提供了可靠的解决方案。在面对下载问题时,可以尝试多种策略以获取Erlang安装包,从而继续你的开发工作。

    erlang22最新下载包

    对于开发者来说,了解这些更新和变化至关重要,因为它们可能影响到现有项目的行为,或者提供新的工具和技术来解决特定问题。学习和掌握Erlang22的新特性有助于提升开发效率和应用质量,特别是在构建高并发、分布式...

    erlang port driver test

    2. **数据传输**:Erlang 进程通过端口发送二进制数据到外部程序,外部程序处理后返回结果,数据通过端口返回到 Erlang 进程。Erlang 使用 `port_command/2` 发送数据,`port_recv/2` 接收数据。 3. **同步通信**:...

Global site tag (gtag.js) - Google Analytics