erlang的Socket參數含義

http://blog.csdn.net/pkutao/article/details/8572216html

{ok, Listen} = gen_tcp:listen(?defPort, [binary, {packet, 2},{reuseaddr, true},{active, true}]),
%gen_tcp表用TCP鏈接
%binary表二進制流方式
%packet,2:表包頭長度2字節
%reuseaddr, true:表多個實例可重用同一端口
% {active,true} 建立一個主動套字節(非阻塞)
% {active,false} 建立一個被動套字節(阻塞),若是爲false表必須手工處理阻塞,不然阻塞在此處沒法收聽,當前我沒法處理
%{active, once} 建立一個一次性被動套字節(阻塞),只收聽一次後堵塞,必須調用inet:setopts(Socket, [{active, once}]),後纔可收聽下一條
% {active,once} 建立一個主動套字節僅接收一條消息,如想接收下一條必須再次激活(半阻塞)shell

 

packet是erlang網絡編程中使用頻率較高的一個參數,例如:編程

gen_tcp:listen(Port, [binary, {active, true}, {packet, 2}]) 
表示接收到的包頭有兩個字節:
receive 
           {tcp, Socket, Binary} ->
接收到的Binary中將不包含2字節的包頭,包頭會剝離,咱們收到的將只是單純的Body,這極大的方便了咱們編程。
 
packet支持的參數有:
raw | 0
未完成的packeting,即無論數據包頭,而是根據langth參數接收數據。
 
1 | 2 | 4
表示包頭的長度,分別是1,2,4個字節(在大端字節序中還包含一個無符號整型),當設置了此參數時,接收到數據後將自動剝離對應長度的頭部,只保留Body。
 
    asn1 | cdr | sunrm | fcgi | tpkt | line
設置以上參數時,應用程序將保證數據包頭部的正確性,可是在gen_tcp:recv2,3接收到的數據包中並不剝離頭部。
 
    http | http_bin
設置以上參數,收到的數據將被erlang:decode_packet/3格式化,在被動模式下將收到{ok, HttpPacket},主動模式下將收到{http, Socket, HttpPacket}.     

 

 

erlang 解決socket 數據粘包問題緩存

咱們知道,erlang實現的網絡服務器性能很是高。erlang的高效不在於短短几行代碼就能寫出一個服務端程序,而在於不用太多代碼,也可以寫出一個高效的服務端程序。而這一切的背後就是erlang對不少網絡操做實現了近乎完美的封裝,使得咱們受益其中。文章將討論erlang gen_tcp 數據連包問題及erlang的解決方案。服務器

數據連包問題,這個在client/server的通信中很常見。就是,當client在極短的時間內發送多個包給server,這時server在接收數據的時候可能發生連包問題,就一次性接收這幾個包的數據,致使數據都粘連在一塊兒。網絡

這裏先討論{packet, raw}或者{packet,0}的狀況,分別看下{active, Boolean}的兩種方式:app

gen_tcp對socket數據封包的獲取有如下2種方式,socket

一、{active, false} 方式經過 gen_tcp:recv(Socket, Length)  -> {ok, Data} | {error, Reason} 來接收。
二、{active, true} 方式以消息形式{tcp, Socket, Data} | {tcp_closed, Socket} 主動投遞給線程。tcp

對於第一種方式 gen_tcp:recv/2,3,若是封包的類型是{packet, raw}或者{packet,0},就須要顯式的指定長度,不然封包的長度是對端決定的,長度只能設置爲0。若是長度Length設置爲0,gen_tcp:recv/2,3會取出Socket接收緩衝區全部的數據oop

對於第二種方式,緩存區有多少數據,都會所有以消息{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

 

[plain]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. -module(tcp_test).  
  2. -export([  
  3.     start_server/0,  
  4.     start_client_unpack/0, start_client_packed/0  
  5.     ]).  
  6.   
  7. -define(PORT, 8888).  
  8. -define(PORT2, 8889).  
  9.   
  10. start_server()->  
  11.     {ok, ListenSocket} = gen_tcp:listen(?PORT, [binary,{active,false}]),  
  12.     {ok, ListenSocket2} = gen_tcp:listen(?PORT2, [binary,{active,false},{packet,2}]),  
  13.     spawn(fun() -> accept(ListenSocket) end),  
  14.     spawn(fun() -> accept(ListenSocket2) end),  
  15.     receive  
  16.         _ -> ok  
  17.     end.  
  18.   
  19. accept(ListenSocket)->  
  20.     case gen_tcp:accept(ListenSocket) of  
  21.         {ok, Socket} ->  
  22.             spawn(fun() -> accept(ListenSocket) end),  
  23.             loop(Socket);  
  24.         _ ->  
  25.             ok  
  26.     end.  
  27.   
  28. loop(Socket)->  
  29.     case gen_tcp:recv(Socket,0) of  
  30.         {ok, Data}->  
  31.             io:format("received message ~p~n", [Data]),  
  32.             gen_tcp:send(Socket, "receive successful"),  
  33.             loop(Socket);  
  34.         {error, Reason}->  
  35.             io:format("socket error: ~p~n", [Reason])  
  36.     end.  
  37.   
  38. start_client_unpack()->  
  39.     {ok,Socket} = gen_tcp:connect({127,0,0,1},?PORT,[binary,{active,false}]),  
  40.     gen_tcp:send(Socket, "1"),  
  41.     gen_tcp:send(Socket, "2"),  
  42.     gen_tcp:send(Socket, "3"),  
  43.     gen_tcp:send(Socket, "4"),  
  44.     gen_tcp:send(Socket, "5"),  
  45.     sleep(1000).  
  46.   
  47. start_client_packed()->  
  48.     {ok,Socket} = gen_tcp:connect({127,0,0,1},?PORT2,[binary,{active,false},{packet,2}]),  
  49.     gen_tcp:send(Socket, "1"),  
  50.     gen_tcp:send(Socket, "2"),  
  51.     gen_tcp:send(Socket, "3"),  
  52.     gen_tcp:send(Socket, "4"),  
  53.     gen_tcp:send(Socket, "5"),  
  54.     sleep(1000).  
  55.   
  56. sleep(Count) ->  
  57.     receive  
  58.     after Count ->  
  59.         ok  
  60.     end.  

運行以下:

[plain]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. C:\>erlc tcp_test.erl  
  2. C:\>erl -s tcp_test start_server  
  3. Eshell V5.10.2  (abort with ^G)  
  4.   
  5. 1> tcp_test:start_client_packed().  
  6. received message <<"1">>  
  7. received message <<"2">>  
  8. received message <<"3">>  
  9. received message <<"4">>  
  10. received message <<"5">>  
  11. ok  
  12.   
  13. 2> tcp_test:start_client_unpack().  
  14. received message <<"12345">>  
  15. 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++爲例

 

[cpp]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. BOOL IsBigEndian()    
  2. {    
  3.     int a = 0x1234;    
  4.     char b =  *(char *)&a;  //經過將int強制類型轉換成char單字節,經過判斷起始存儲位置    
  5.     if( b == 0x12)    
  6.     {    
  7.         return TRUE;    
  8.     }    
  9.     return FALSE;    
  10. }  

如何轉換字節序,以C++爲例

[cpp]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. // 32位字數據  
  2. #define LittletoBig32(A)   ((( (UINT)(A) & 0xff000000) >> 24) | \  
  3.     (( (UINT)(A) & 0x00ff0000) >> 8)   | \  
  4.     (( (UINT)(A) & 0x0000ff00) << 8)   | \  
  5.     (( (UINT)(A) & 0x000000ff) << 24))  
  6.   
  7. // 16位字數據  
  8. #define LittletoBig16(A)   (( ((USHORT)(A) & 0xff00) >> 8)    | \  
  9.     (( (USHORT)(A) & 0x00ff) << 8))  

 

參考

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

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

 

 

 

 

seq_loop(Listen).

 

listen_socket(Socket,Mode)->receive {tcp,Socket,Bin} ->%process_req(Bin),%MsgQueueSize->得到有多少個消息包積壓,先使用{active, true},若是判斷到接收到的消息包太多,再改爲 {active, once}。{message_queue_len, MsgQueueSize} = erlang:process_info(self(),message_queue_len),io:format("Server received binary = ~p~n",[Bin]),io:format("Queue size is ~p~n", [MsgQueueSize]),if(MsgQueueSize > 500) and (Mode =:= active_true) ->inet:setopts(Socket, [{active, once}]), %再次調用收聽,開始收聽下一條信息 listen_socket(Socket, active_once);(MsgQueueSize < 10) and (Mode =:= active_once) ->inet:setopts(Socket, [{active, true}]),   listen_socket(Socket, active_true)true->io:format("Queue size is ~p~n", [MsgQueueSize]),   listen_socket(Socket, Mode)end;{tcp_closed,Socket} ->io:format("有人下線 =>~n"),io:format("Client socket closed ~n")end.

相關文章
相關標籤/搜索