非常教程

Erlang 20参考手册

核心 | kernel

gen_tcp

模块

调用 gen_tcp

模块摘要

接口到 TCP / IP 套接字。

描述

此模块提供了使用 TCP/IP 协议与套接字通信的功能。

以下代码片段是一个客户端连接到端口5678的服务器,传输二进制文件并关闭连接的简单示例:

client() ->
    SomeHostInNet = "localhost", % to make it runnable on one machine
    {ok, Sock} = gen_tcp:connect(SomeHostInNet, 5678, 
                                 [binary, {packet, 0}]),
    ok = gen_tcp:send(Sock, "Some Data"),
    ok = gen_tcp:close(Sock).

在另一端,服务器正在侦听端口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.

有关更多示例,请参见Examples部分。

数据类型

option() =

{active, true | false | once | -32768..32767} |

{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()} |

{show_econnreset, 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 |

show_econnreset |

sndbuf |

tos |

ipv6_v6only

connect_option() =

{ip,inet:socket_address()} |

{fd, Fd :: integer() >= 0} |

{ifaddr,inet:socket_address()} |

inet:address_family()|

{port,inet:port_number()} |

{tcp_module, module()} |

option()

listen_option() =

{ip,inet:socket_address()} |

{fd, Fd :: integer() >= 0} |

{ifaddr,inet:socket_address()} |

inet:address_family()|

{port,inet:port_number()} |

{backlog, B :: integer() >= 0} |

{tcp_module, module()} |

option()

socket()

accept/1,2和返回connect/3,4

出口

接受(ListenSocket) - > {ok,Socket} | {错误,原因}

接受(ListenSocket,Timeout) - > {ok,Socket} | {错误,原因}

类型

listen/2返回。

在侦听套接字上接受传入的连接请求。Socket必须是从中返回的套接字listen/2Timeout指定以毫秒为单位的超时值。默认为infinity

返回:

  • {ok, Socket} 如果建立连接
  • {error, closed}如果ListenSocket关闭
  • {error, timeout} 如果在规定的时间内没有建立连接
  • {error, system_limit} 如果 Erlang 仿真器中的所有可用端口都在使用中
  • 如果出现其他问题,请inet(3)输入 POSIX 错误值,查看可能的错误值

数据包可以被发送到返回的插座Socket使用send/2。从对等体发送的数据包作为消息发送(除非{active, false}在侦听套接字的选项列表中指定,在这种情况下,通过调用来检索数据包recv/2):

{tcp, Socket, Data}

注意

accept调用并没有必须从插座所有者进程发出。使用仿真器5.5.3及更高版本,可以从不同进程发出多个同时接受调用,这允许接收器进程池处理传入连接。

关闭(套接字) - >确定 (close(Socket) -> ok)

类型

关闭 TCP 套接字。

请注意,在 TCP 的大多数实现中,执行close不能保证在远程端检测到关闭之前发送的任何数据都会传送给收件人。如果你想保证将数据传递给收件人,有两种常见的方法来实现这一点。

  • 用于gen_tcp:shutdown(Sock, write)表示不再发送数据,并等待关闭套接字的读取侧。
  • 使用套接字选项{packet, N}(或类似的东西)可以使接收方在知道接收到所有数据时关闭连接。

连接(地址,端口,选项) - > {ok,Socket} | {错误,原因}(connect(Address, Port, Options) -> {ok, Socket} | {error, Reason})

连接(地址,端口,选项,超时) - >

{ok,Socket} | {错误,原因}

类型

Port使用 IP 地址连接到主机上 TCP 端口上的服务器Address。参数Address可以是主机名或 IP 地址。

以下选项可用:

{ip, Address}

如果主机有许多网络接口,则此选项指定使用哪一个。

{ifaddr, Address}

{ip, Address}一样。如果主机有许多网络接口,则此选项指定使用哪一个。

{fd, integer() >= 0}

如果套接字以某种方式连接而没有使用gen_tcp,使用此选项传递其文件描述符。如果{ip, Address}和/或{port, port_number()}与此选项结合,则fd在连接之前绑定到指定的接口和端口。如果未指定这些选项,则假定fd已经被适当的约束了。

inet

设置 IPv 4 的套接字。

inet6

设置 IPv 6 的套接字。

local

设置 Unix 域套接字。见inet:local_address()

{port, Port}

指定要使用的本地端口号。

{tcp_module, module()}

重写使用的回调模块。默认为inet_tcp为 IPv 4 和inet6_tcp用于 IPv 6。

Opt

inet:setopts/2

可以将数据包发送到返回的套接字。Socket使用send/2从对等方发送的数据包以消息的形式传递:

{tcp, Socket, Data}

如果套接字处于{active, N}模式(请参阅inet:setopts/2以了解详细信息)并且其消息计数器下降到0,则传递以下消息以指示套接字已转换为被动({active, false})模式:

{tcp_passive, Socket}

如果套接字关闭,则传递以下消息:

{tcp_closed, Socket}

如果套接字上发生错误,则传递以下消息(除非{active, false}在套接字的选项列表中指定,在这种情况下通过调用检索数据包recv/2):

{tcp_error, Socket, Reason}

任选Timeout参数指定超时(以毫秒为单位)。默认为infinity

注意

指定的选项的默认值connect可能受内核配置参数影响inet_default_connect_options。有关详情,请参阅inet(3)

controls_process(Socket,Pid) - > ok | {错误,原因}

类型

指定一个新的控制过程PidSocket控制过程是从套接字接收消息的过程。如果被当前控制进程以外的任何其他进程调用,{error, not_owner}会被归还。如果由Pid不是现有的局部PID,{error, badarg}会被归还。{error, badarg}在某些情况下,也可能会在下列情况下返回Socket在执行此函数期间关闭。

如果套接字设置为主动模式,则此功能将把主叫方邮箱中的所有消息传输到新的控制进程。如果有任何其他进程在传输过程中与套接字进行交互,则传输可能无法正常工作,并且邮件可能保留在主叫方的邮箱中。例如,在转移完成之前更改套接字活动模式可能会导致此问题。

听(端口,选项) - > {ok,ListenSocket} | {错误,原因}

类型

设置一个套接字来监听端口Port在当地的主机上。

如果Port == 0,基础操作系统分配可用的端口号,请使用inet:port/1去找回它。

可供选择的办法如下:

list

收到Packet是作为一个列表交付的。

binary

收到Packet作为二进制文件传递。

{backlog, B}

B是整数>=0。待定值定义挂起连接的队列可以增长到的最大长度。默认为5

{ip, Address}

如果主机有许多网络接口,则此选项指定要监听哪个接口。

{port, Port}

指定要使用的本地端口号。

{fd, Fd}

如果套接字以某种方式连接而不使用gen_tcp,请使用此选项为其传递文件描述符。

{ifaddr, Address}

{ip, Address}一样。如果主机有许多网络接口,则此选项指定使用哪一个。

inet6

设置 IPv 6 的套接字。

inet

设置 IPv 4 的套接字。

{tcp_module, module()}

重写使用的回调模块。默认为inet_tcp为 IPv 4 和inet6_tcp用于 IPv 6。

Opt

inet:setopts/2

返回的套接字ListenSocket只能用于调用accept/1,2

注意

指定的选项的默认值listen可能受内核配置参数影响inet_default_listen_options。有关详情,请参阅inet(3)

recv(Socket,Length) - > {ok,Packet} | {错误,原因}

recv(Socket,Length,Timeout) - > {ok,Packet} | {错误,原因}

类型

   See the description of `HttpPacket` in `erlang:decode_packet/3` in ERTS.    

从被动模式的套接字接收数据包。关闭的套接字由返回值指示{error, closed}

参数Length只有在套接字处于raw模式时才有意义,并且表示要读取的字节数。如果Length0,则返回所有可用的字节。如果Length> 0Length返回确切的字节或错误; Length当套接字从另一端关闭时可能丢弃少于数据字节的数据。

可选Timeout参数指定超时(以毫秒为单位)。默认为infinity

发送(Socket,Packet) - > ok | {错误,原因}

类型

在套接字上发送数据包。

没有send超时选项的调用,send_timeout如果需要超时,请使用套接字选项。参见章节Examples

关闭(Socket,How) - > ok | {错误,原因}

类型

在一个或两个方向上关闭套接字。

How == write 意味着关闭写入套接字,读取它仍然是可能的。

如果端口中How == read没有缓冲输出数据Socket,则立即关闭套接字,并返回遇到的任何错误Reason

如果在套接字端口中有数据缓存,则关闭套接字的尝试将被推迟,直到数据写入内核套接字发送缓冲区。如果遇到任何错误,套接字将被关闭并{error, closed}在下一个recv/2或返回send/2

如果对端在写入端执行了关闭操作,则该选项{exit_on_close, false}非常有用。

实例

下面的示例演示选项的使用{active,once}通过实现服务器作为多个工作进程在单个侦听套接字上执行接受,多个用户接受。功能start/2获取要侦听传入连接的辅助进程数和端口号。如果LPort指定为0,使用的是一个临时端口号,这就是开始函数返回分配的实际端口号的原因:

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调用不接受超时选项,因为发送超时是通过套接字选项处理的send_timeout。没有接收器的发送操作的行为主要由底层TCP堆栈和网络基础结构定义。编写处理挂起接收器的代码,最终可能会导致发件人挂起,send如下所示。

考虑从客户端进程接收数据并将其转发到网络上的服务器的过程。该进程通过 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就派上用场了。

Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1