概述node
TCP/IP套接字接口服務器
描述網絡
gen_tcp模塊提供了使用TCP / IP協議與套接字進行通訊的功能。socket
如下代碼片斷提供了一個客戶端鏈接到端口5678的服務器的簡單示例,傳輸一個二進制文件並關閉鏈接:tcp
client() ->ide
SomeHostInNet = "localhost", % to make it runnable on one machine函數
{ok, Sock} = gen_tcp:connect(SomeHostInNet, 5678, [binary, {packet, 0}]),oop
ok = gen_tcp:send(Sock, "Some Data"),this
ok = gen_tcp:close(Sock).idea
在另外一端,服務器正在偵聽端口5678,接受鏈接並接收二進制文件:
server() ->
{ok, LSock} = gen_tcp:listen(5678, [binary, {packet, 0},
{active, false}]),
{ok, Sock} = gen_tcp:accept(LSock),
{ok, Bin} = do_recv(Sock, []),
ok = gen_tcp:close(Sock),
Bin.
do_recv(Sock, Bs) ->
case gen_tcp:recv(Sock, 0) of
{ok, B} ->
do_recv(Sock, [Bs, B]);
{error, closed} ->
{ok, list_to_binary(Bs)}
end.
有關更多示例,請參閱示例部分。
數據類型
option() = {active, true | false | once}
| {buffer, integer() >= 0}
| {delay_send, boolean()}
| {deliver, port | term}
| {dontroute, boolean()}
| {exit_on_close, boolean()}
| {header, integer() >= 0}
| {high_msgq_watermark, integer() >= 1}
| {high_watermark, integer() >= 0}
| {keepalive, boolean()}
| {linger, {boolean(), integer() >= 0}}
| {low_msgq_watermark, integer() >= 1}
| {low_watermark, integer() >= 0}
| {mode, list | binary}
| list
| binary
| {nodelay, boolean()}
| {packet,
0 |
1 |
2 |
4 |
raw |
sunrm |
asn1 |
cdr |
fcgi |
line |
tpkt |
http |
httph |
http_bin |
httph_bin}
| {packet_size, integer() >= 0}
| {priority, integer() >= 0}
| {raw,
Protocol :: integer() >= 0,
OptionNum :: integer() >= 0,
ValueBin :: binary()}
| {recbuf, integer() >= 0}
| {reuseaddr, boolean()}
| {send_timeout, integer() >= 0 | infinity}
| {send_timeout_close, boolean()}
| {sndbuf, integer() >= 0}
| {tos, integer() >= 0}
| {ipv6_v6only, boolean()}
option_name() = active
| buffer
| delay_send
| deliver
| dontroute
| exit_on_close
| header
| high_msgq_watermark
| high_watermark
| keepalive
| linger
| low_msgq_watermark
| low_watermark
| mode
| nodelay
| packet
| packet_size
| priority
| {raw,
Protocol :: integer() >= 0,
OptionNum :: integer() >= 0,
ValueSpec :: (ValueSize :: integer() >= 0)
| (ValueBin :: binary())}
| recbuf
| reuseaddr
| send_timeout
| send_timeout_close
| sndbuf
| tos
| ipv6_v6only
connect_option() = {ip, inet:ip_address()}
| {fd, Fd :: integer() >= 0}
| {ifaddr, inet:ip_address()}
| inet:address_family()
| {port, inet:port_number()}
| {tcp_module, module()}
| option()
listen_option() = {ip, inet:ip_address()}
| {fd, Fd :: integer() >= 0}
| {ifaddr, inet:ip_address()}
| inet:address_family()
| {port, inet:port_number()}
| {backlog, B :: integer() >= 0}
| {tcp_module, module()}
| option()
socket()
由accept/ 1,2和connect/ 3,4返回。
導出
connect(Address, Port, Options) -> {ok, Socket} | {error, Reason}
connect(Address, Port, Options, Timeout) -> {ok, Socket} | {error, Reason}
Types:
Address = inet:ip_address() | inet:hostname()
Port = inet:port_number()
Options = [connect_option()]
Timeout = timeout()
Socket = socket()
Reason = inet:posix()
鏈接到IP地址爲Address的主機上的TCP端口Port上的服務器。 Address參數能夠是主機名或IP地址。
{ip, ip_address()}
若是主機有多個網絡接口,則此選項指定要使用哪個。
{ifaddr, ip_address()}
與{ip, ip_address()}相同。 若是主機有多個網絡接口,則此選項指定要使用哪個。
{fd, integer() >= 0}
若是某個套接字在不使用gen_tcp的狀況下以某種方式鏈接,請使用此選項爲其傳遞文件描述符。
inet
設置IPv4的套接字。
inet6
設置IPv6的套接字。
{port, Port}
指定要使用的本地端口號。
{tcp_module, module()}
覆蓋使用哪一個回調模塊。 默認爲IPv4的inet_tcp和IPv6的inet6_tcp。
Opt
參見 inet:setopts/2.
能夠使用send / 2將數據包發送到返回的套接字Socket。 從對等方發送的數據包將做爲消息發送:
{tcp, Socket, Data}
若是套接字已關閉,則會傳遞如下消息:
{tcp_closed, Socket}
若是套接字上發生錯誤,則傳遞如下消息:
{tcp_error, Socket, Reason}
除非在套接字的選項列表中指定{active,false},在這種狀況下,經過調用recv/ 2來檢索數據包。
可選的Timeout參數指定以毫秒爲單位的超時。 默認值是無窮大。
注意:
給予鏈接的選項的默認值可能受內核配置參數inet_default_connect_options的影響。 有關詳細信息,請參閱inet(3)。
listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}
Types:
Port = inet:port_number()
Options = [listen_option()]
ListenSocket = socket()
Reason = system_limit | inet:posix()
設置套接字以偵聽本地主機上的端口Port。
若是Port== 0,則底層操做系統會分配一個可用端口號,請使用inet:port/1來檢索它。
可用的選項是:
list
接收到的數據包做爲列表提供。
binary
接收到的數據包以二進制形式提供。
{backlog, B}
B是>= 0的整數。backlog值默認爲5。backlog值定義待處理鏈接隊列可能增加到的最大長度。
{ip, ip_address()}
若是主機有多個網絡接口,則此選項指定要監聽哪一個接口。
{port, Port}
指定要使用的本地端口號。
{fd, Fd}
若是某個套接字在不使用gen_tcp的狀況下以某種方式鏈接,請使用此選項爲其傳遞文件描述符。
{ifaddr, ip_address()}
與{ip,ip_address()}相同。 若是主機有多個網絡接口,則此選項指定要使用哪個。
inet
設置IPv4的套接字。
inet6
設置IPv6的套接字。
{tcp_module, module()}
覆蓋使用哪一個回調模塊。 默認爲IPv4的inet_tcp和IPv6的inet6_tcp。
Opt
參見 inet:setopts/2。
返回的套接字ListenSocket只能用於accept/1,2的調用。
注意:
監聽選項的默認值可能受內核配置參數inet_default_listen_options的影響。有關詳細信息,請參閱inet(3)。
accept(ListenSocket) -> {ok, Socket} | {error, Reason}
accept(ListenSocket, Timeout) -> {ok, Socket} | {error, Reason}
Types:
ListenSocket = socket()
listen/2返回。
Timeout = timeout()
Socket = socket()
Reason = closed | timeout | system_limit | inet:posix()
在偵聽套接字上接受傳入的鏈接請求。套接字必須是從listen / 2返回的套接字。 超時以ms爲單位指定超時值,默認爲無窮大。
若是鏈接已創建,則返回{ok,Socket};若是ListenSocket已關閉,則返回{error,closed};若是在指定的時間內未創建鏈接,則返回{error,timeout};若是全部可用端口都處於鏈接狀態,則返回{error,system_limit} 。 若是出現其餘問題,也可能返回一個POSIX錯誤值,請參閱inet(3)瞭解可能的錯誤值。
能夠使用send/2將數據包發送到返回的套接字Socket。從對等方發送的數據包將做爲消息發送:
{tcp, Socket, Data}
除非在偵聽套接字的選項列表中指定了{active,false},在這種狀況下,經過調用recv/2來檢索數據包。
注意:
值得注意的是,接受調用沒必要從套接字全部者進程發出。 使用仿真器5.5.3及更高版本,能夠從不一樣進程發出多個同時接受調用,這容許接收器進程池處理傳入鏈接。
send(Socket, Packet) -> ok | {error, Reason}
Types:
Socket = socket()
Packet = iodata()
Reason = closed | inet:posix()
在套接字上發送數據包。
發送調用沒有超時選項,若是須要超時,能夠使用send_timeout套接字選項。請參閱示例部分。
recv(Socket, Length) -> {ok, Packet} | {error, Reason}
recv(Socket, Length, Timeout) -> {ok, Packet} | {error, Reason}
Types:
Socket = socket()
Length = integer() >= 0
Timeout = timeout()
Packet = string() | binary() | HttpPacket
Reason = closed | inet:posix()
HttpPacket = term()
請參閱erlang中的HttpPacket說明:decode_packet/3。.
該函數以被動模式從套接字接收數據包。關閉的套接字由返回值{error,closed}表示。
Length參數僅在套接字處於原始模式時纔有意義,而且表示要讀取的字節數。 若是Length = 0,則返回全部可用的字節。 若是長度> 0,則返回確切的長度字節或錯誤; 當套接字從另外一端關閉時可能丟棄少於Length數據的字節數據。
可選的Timeout參數指定以毫秒爲單位的超時。默認值是無窮大。
controlling_process(Socket, Pid) -> ok | {error, Reason}
Types:
Socket = socket()
Pid = pid()
Reason = closed | not_owner | inet:posix()
爲Socket分配一個新的控制進程Pid。 控制過程是從套接字接收消息的過程。 若是被當前控制進程之外的任何其餘進程調用,則返回{error,not_owner}。
close(Socket) -> ok
Types:
Socket = socket()
關閉TCP套接字。
shutdown(Socket, How) -> ok | {error, Reason}
Types:
Socket = socket()
How = read | write | read_write
Reason = inet:posix()
當即關閉一個或兩個方向的套接字。
How==write意味着關閉寫入套接字,從它讀取仍然是可能的。
爲了可以處理對端在寫入端執行關閉操做,{exit_on_close,false}選項頗有用。
例子
如下示例經過將服務器實現爲在單個偵聽套接字上進行接受的多個工做進程來講明{active,once}選項和多個接受的用法。 start/ 2函數使用工做進程的數量以及端口號監聽即將到來的鏈接。 若是LPort指定爲0,則使用臨時端口號,爲何start函數返回分配的實際端口號:
start(Num,LPort) ->
case gen_tcp:listen(LPort,[{active, false},{packet,2}]) of
{ok, ListenSock} ->
start_servers(Num,ListenSock),
{ok, Port} = inet:port(ListenSock),
Port;
{error,Reason} ->
{error,Reason}
end.
start_servers(0,_) ->
ok;
start_servers(Num,LS) ->
spawn(?MODULE,server,[LS]),
start_servers(Num-1,LS).
server(LS) ->
case gen_tcp:accept(LS) of
{ok,S} ->
loop(S),
server(LS);
Other ->
io:format("accept returned ~w - goodbye!~n",[Other]),
ok
end.
loop(S) ->
inet:setopts(S,[{active,once}]),
receive
{tcp,S,Data} ->
Answer = process(Data), % Not implemented in this example
gen_tcp:send(S,Answer),
loop(S);
{tcp_closed,S} ->
io:format("Socket ~w closed [~w]~n",[S,self()]),
ok
end.
一個簡單的客戶端多是這樣的:
client(PortNo,Message) ->
{ok,Sock} = gen_tcp:connect("localhost",PortNo,[{active,false},{packet,2}]),
gen_tcp:send(Sock,Message),
A = gen_tcp:recv(Sock,0),
gen_tcp:close(Sock),
A.
發送調用不接受超時選項這一事實是由於發送超時是經過套接字選項send_timeout處理的。沒有接收器的發送操做的行爲在很大程度上由底層TCP堆棧以及網絡基礎結構定義。 若是想編寫處理掛起的接收器的代碼,最終可能會致使發送者掛起發送調用,則能夠編寫以下代碼。
考慮一個從客戶端進程接收數據的進程,該進程將被轉發到網絡上的服務器。該進程已經過TCP / IP鏈接到服務器,而且不會對其發送的每條消息進行確認,但必須依賴發送超時選項來檢測另外一端是否無響應。鏈接時咱們能夠使用send_timeout選項:
...
{ok,Sock} = gen_tcp:connect(HostAddress, Port,[{active,false},{send_timeout, 5000},{packet,2}]),
loop(Sock), % See below
...
在處理請求的循環中,咱們如今能夠檢測發送超時:
loop(Sock) ->
receive
{Client, send_data, Binary} ->
case gen_tcp:send(Sock,[Binary]) of
{error, timeout} ->
io:format("Send timeout, closing!~n",[]),
handle_send_timeout(), % Not implemented here
Client ! {self(),{error_sending, timeout}},
%% Usually, it's a good idea to give up in case of a
%% send timeout, as you never know how much actually
%% reached the server, maybe only a packet header?!
gen_tcp:close(Sock);
{error, OtherSendError} ->
io:format("Some other error on socket (~p), closing",[OtherSendError]),
Client ! {self(),{error_sending, OtherSendError}},
gen_tcp:close(Sock);
ok ->
Client ! {self(), data_sent},
loop(Sock)
end
end.
一般,只需檢測接收超時就足夠了,由於大多數協議都包含來自服務器的某種確認,但若是協議是嚴格意義上的一種方法,那麼send_timeout選項就派上用場了!