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網絡編程中使用頻率較高的一個參數,例如:編程
咱們知道,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
運行以下:
字節序
字節序分爲兩類: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++爲例
如何轉換字節序,以C++爲例
參考
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.