查看源代码 gen_tcp (kernel v10.2)

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),
    ok = gen_tcp:close(LSock),
    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.

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

注意

创建套接字的函数可以接受一个可选的选项;{inet_backend, Backend},如果指定,则必须是第一个选项。 这会选择平台套接字 API 的实现后端。

这是一个临时选项,将在未来的版本中被忽略。

默认值为 Backend = inet,它选择传统的 inet_drv.c 驱动程序。另一个选择是 Backend = socket,它选择新的 socket 模块及其 NIF 实现。

当节点使用应用程序 kernel 的配置变量 inet_backend 启动时,可以更改系统默认值。

对于 gen_tcpinet_backend = socket,我们尝试尽可能地保持“兼容”,但这有时是不可能的。以下是 inet 后端 inet(默认)和 socket 的行为不同的情况列表:

  • 非阻塞发送

    如果用户调用 gen_tcp:send/2inet_backend = inet,尝试发送的数据量超过了操作系统缓冲区中的空间,则“剩余数据”会由 inet 驱动程序缓冲(稍后在后台发送)。用户看到的效果是调用是非阻塞的。

    inet_backend = socket 时,效果不是这样,因为没有缓冲。相反,用户会一直挂起,直到所有数据都已发送或达到 send_timeout 超时为止。

  • shutdown/2 可能会隐藏错误

    该调用不涉及接收进程状态,直接在底层套接字上完成。例如,在 Linux 上,众所周知的一个错误行为是它会跳过一些检查,因此在监听套接字上执行关闭操作会返回 ok,而逻辑结果应该是 {error, enotconn}inet_drv.c 驱动程序执行了额外的检查并模拟了正确的错误,但是使用 Backend = socket 会引入涉及接收进程的开销。

  • 选项 nodelay 是一个 TCP 特定选项,它与 domain = local 兼容。

    当使用 inet_backend = socket 时,尝试创建具有 domain = local 的套接字(通过监听或连接)(例如使用选项 {ifaddr, {local,"/tmp/test"}})将失败,并返回 {error, enotsup}

    这对于 inet_backend = inet 实际上也不起作用,但在那种情况下,错误会被简单地忽略,这是一个主意。我们选择对于 inet_backend = socket 忽略此错误。

  • 异步关闭写入

    在以 inet_backend = socket 创建的套接字上调用 gen_tcp:shutdown(Socket, write | read_write) 将会立即生效,这与以 inet_backend = inet 创建的套接字不同。

    有关更多信息,请参阅 异步关闭写入

  • Windows 要求套接字(domain = inet | inet6)进行绑定。

    目前,在 Windows 上使用 inet_backend = socket 创建的所有套接字都将被绑定。如果用户未提供地址,则 gen_tcp 将尝试自行“计算出”地址。

示例

以下示例说明了如何使用选项 {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 会派上用场。

概要

函数

接受监听套接字上的传入连接请求。

关闭 TCP 套接字。

创建一个连接到指定地址的套接字。

创建一个连接到指定地址的套接字。

更改套接字的控制进程(所有者)。

创建监听套接字。

被动模式下的套接字接收数据包。

在套接字上发送数据包。

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

类型

-type connect_option() ::
          {fd, Fd :: non_neg_integer()} |
          inet:address_family() |
          {ifaddr, socket:sockaddr_in() | socket:sockaddr_in6() | inet:socket_address()} |
          {ip, inet:socket_address()} |
          {port, inet:port_number()} |
          {tcp_module, module()} |
          {netns, file:filename_all()} |
          {bind_to_device, binary()} |
          option().
-type listen_option() ::
          {fd, Fd :: non_neg_integer()} |
          inet:address_family() |
          {ifaddr, socket:sockaddr_in() | socket:sockaddr_in6() | inet:socket_address()} |
          {ip, inet:socket_address()} |
          {port, inet:port_number()} |
          {backlog, B :: non_neg_integer()} |
          {tcp_module, module()} |
          {netns, file:filename_all()} |
          {bind_to_device, binary()} |
          option().
-type option() ::
          {active, true | false | once | -32768..32767} |
          {buffer, non_neg_integer()} |
          {debug, boolean()} |
          {delay_send, boolean()} |
          {deliver, port | term} |
          {dontroute, boolean()} |
          {exit_on_close, boolean()} |
          {exclusiveaddruse, boolean()} |
          {header, non_neg_integer()} |
          {high_msgq_watermark, pos_integer()} |
          {high_watermark, non_neg_integer()} |
          {keepalive, boolean()} |
          {linger, {boolean(), non_neg_integer()}} |
          {low_msgq_watermark, pos_integer()} |
          {low_watermark, non_neg_integer()} |
          {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, non_neg_integer()} |
          {priority, non_neg_integer()} |
          {raw, Protocol :: non_neg_integer(), OptionNum :: non_neg_integer(), ValueBin :: binary()} |
          {recbuf, non_neg_integer()} |
          {reuseaddr, boolean()} |
          {reuseport, boolean()} |
          {reuseport_lb, boolean()} |
          {send_timeout, non_neg_integer() | infinity} |
          {send_timeout_close, boolean()} |
          {show_econnreset, boolean()} |
          {sndbuf, non_neg_integer()} |
          {tos, non_neg_integer()} |
          {tclass, non_neg_integer()} |
          {ttl, non_neg_integer()} |
          {recvtos, boolean()} |
          {recvtclass, boolean()} |
          {recvttl, boolean()} |
          {ipv6_v6only, boolean()}.
-type option_name() ::
          active | buffer | debug | delay_send | deliver | dontroute | exit_on_close |
          exclusiveaddruse | header | high_msgq_watermark | high_watermark | keepalive | linger |
          low_msgq_watermark | low_watermark | mode | nodelay | packet | packet_size | priority |
          {raw,
           Protocol :: non_neg_integer(),
           OptionNum :: non_neg_integer(),
           ValueSpec :: (ValueSize :: non_neg_integer()) | (ValueBin :: binary())} |
          recbuf | reuseaddr | reuseport | reuseport_lb | send_timeout | send_timeout_close |
          show_econnreset | sndbuf | tos | tclass | ttl | recvtos | recvtclass | recvttl | pktoptions |
          ipv6_v6only.
-type pktoptions_value() :: {pktoptions, inet:ancillary_data()}.

来自套接字选项 pktoptions 的值。

如果平台为套接字实现了 IPv4 选项 IP_PKTOPTIONS,或 IPv6 选项 IPV6_PKTOPTIONSIPV6_2292PKTOPTIONS;当使用选项名称 pktoptions 调用时,此值从 inet:getopts/2 返回。

注意

此选项似乎是特定于 Linux 的,并且它在未来的 Linux 内核版本中的存在也令人担忧,因为该选项是 RFC 2292 的一部分,该 RFC 早在 (2003) 就被 RFC 3542 废弃,而 RFC 3542 明确删除了从流套接字获取数据包信息的可能性。 为了比较:它曾经在 FreeBSD 中存在,但现在已被删除,至少从 FreeBSD 10 开始。

-type socket() :: inet:socket().

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

函数

-spec accept(ListenSocket) -> {ok, Socket} | {error, Reason}
                when
                    ListenSocket :: socket(),
                    Socket :: socket(),
                    Reason :: closed | system_limit | inet:posix().

等效于 accept(ListenSocket, infinity)

链接到此函数

accept(ListenSocket, Timeout)

查看源代码
-spec accept(ListenSocket, Timeout) -> {ok, Socket} | {error, Reason}
                when
                    ListenSocket :: socket(),
                    Timeout :: timeout(),
                    Socket :: socket(),
                    Reason :: closed | timeout | system_limit | inet:posix().

接受监听套接字上的传入连接请求。

Socket 必须是从 listen/2 返回的套接字。Timeout 指定以毫秒为单位的超时值。 默认为 infinity

返回

  • {ok, Socket} 如果建立连接
  • {error, closed} 如果 ListenSocket 已关闭
  • {error, timeout} 如果在 Timeout 内未建立连接
  • {error, system_limit} 如果 Erlang 模拟器中的所有可用端口都在使用中
  • 如果发生其他错误,则返回 POSIX 错误值,有关可能的值,请参阅 inet

要在返回的 Socket 上发送数据包(出站),请使用 send/2。从对等方发送的数据包(入站)作为消息传递给套接字所有者; 创建套接字的进程。除非在创建 监听套接字时,在选项列表中指定了 {active, false}

有关活动模式套接字消息和被动模式,请参阅 connect/4

注意

accept 调用不必从套接字所有者进程发出。使用 5.5.3 及更高版本的模拟器,可以从不同的进程发出多个同时接受调用,这允许使用一个接受器进程池来处理传入的连接。

-spec close(Socket) -> ok when Socket :: socket().

关闭 TCP 套接字。

请注意,在大多数 TCP 实现中,执行 close 并不能保证发送的数据已传递给接收方。 保证接收方在关闭之前看到所有发送的数据,但发送方没有收到任何指示。

如果发送方需要知道接收方已接收到所有数据,则有两种常用的方法可以实现此目的

  1. 使用 gen_tcp:shutdown(Sock, write) 发出信号,表示不再发送数据,并等待另一方确认看到其读取端已关闭,方法是关闭其写入端,这在此端显示为套接字关闭。
  2. 在 TCP 之上的协议中实现一个双方连接都遵守的确认,指示已看到所有数据。套接字选项 {packet, N} 可能会很有用。
链接到此函数

connect(SockAddr, Opts)

查看源代码 (自 OTP 24.3 起)
-spec connect(SockAddr, Opts) -> {ok, Socket} | {error, Reason}
                 when
                     SockAddr :: socket:sockaddr_in() | socket:sockaddr_in6(),
                     Opts :: [inet:inet_backend() | connect_option()],
                     Socket :: socket(),
                     Reason :: inet:posix().

等效于 connect(SockAddr, Opts, infinity)

-spec connect(Address, Port, Opts) -> {ok, Socket} | {error, Reason}
                 when
                     Address :: inet:socket_address() | inet:hostname(),
                     Port :: inet:port_number(),
                     Opts :: [inet:inet_backend() | connect_option()],
                     Socket :: socket(),
                     Reason :: inet:posix();
             (SockAddr, Opts, Timeout) -> {ok, Socket} | {error, Reason}
                 when
                     SockAddr :: socket:sockaddr_in() | socket:sockaddr_in6(),
                     Opts :: [inet:inet_backend() | connect_option()],
                     Timeout :: timeout(),
                     Socket :: socket(),
                     Reason :: timeout | inet:posix().

创建一个连接到指定地址的套接字。

使用参数 AddressPort

等效于 connect(Address, Port, Opts, infinity)

使用参数 SockAddr (自 OTP 24.3 起)

连接到 SockAddr 指定的远程监听套接字,例如,socket:sockaddr_in6/0 允许指定链接本地 IPv6 地址的 scope_id

也允许使用相同 map/0 格式的 IPv4 地址

除了目标地址的格式之外,等效于 connect/4

链接到此函数

connect(Address, Port, Opts, Timeout)

查看源代码
-spec connect(Address, Port, Opts, Timeout) -> {ok, Socket} | {error, Reason}
                 when
                     Address :: inet:socket_address() | inet:hostname(),
                     Port :: inet:port_number(),
                     Opts :: [inet:inet_backend() | connect_option()],
                     Timeout :: timeout(),
                     Socket :: socket(),
                     Reason :: timeout | inet:posix().

创建一个连接到指定地址的套接字。

创建一个套接字,并将其连接到主机上 TCP 端口 Port 上的服务器,该主机可以使用 IP 地址 Address,也可以使用主机名。

Opts (连接选项)

  • {ip, Address} - 如果本地主机有多个 IP 地址,此选项指定要使用的 IP 地址。

  • {ifaddr, Address} - 与 {ip, Address} 相同。

    但是,如果 Address 实际上是 socket:sockaddr_in/0socket:sockaddr_in6/0,则此选项优先于之前使用 ipport 选项设置的任何值。如果这些选项(ip 或/和 port)出现在此选项之后,则它们可以用于更新此选项的相应字段(对于 ip,更新 addr 字段,对于 port,更新 port 字段)。

  • {fd, integer() >= 0} - 如果套接字在没有使用 gen_tcp 的情况下被连接,则使用此选项传递其文件描述符。如果 {ip, Address} 和/或 {port, port_number()} 与此选项组合使用,则在连接之前,fd 将绑定到指定的接口和端口。如果未指定这些选项,则假定 fd 已正确绑定。

  • inet - 为 IPv4 设置套接字。

  • inet6 - 为 IPv6 设置套接字。

  • local - 设置 Unix 域套接字。请参阅 inet:local_address/0

  • {port, Port} - 指定要使用的本地端口号。

  • {tcp_module, module()} - 覆盖要使用的回调模块。IPv4 默认为 inet_tcp,IPv6 默认为 inet6_tcp

  • option/0 - 请参阅 inet:setopts/2

套接字数据

可以使用 send(Socket, Packet) 向对等方(出站)发送数据包。来自对等方(入站)发送的数据包将作为消息传递给套接字所有者;除非在 Options 列表中指定了 {active, false},否则将传递给创建套接字的进程。

活动模式套接字消息

  • {tcp, Socket, Data} - 来自套接字的入站数据。

  • {tcp_passive, Socket} - 套接字处于 {active, N} 模式(有关详细信息,请参阅 inet:setopts/2),并且其消息计数器达到 0,表明该套接字已转换为被动 ({active, false}) 模式。

  • {tcp_closed, Socket} - 套接字已关闭。

  • {tcp_error, Socket, Reason} - 发生套接字错误。

被动模式

如果在套接字的选项列表中指定了 {active, false},则通过调用 recv/2,3 来检索数据包和错误(send/2 也可能返回错误)。

超时

可选的 Timeout 参数以毫秒为单位指定连接超时时间。默认为 infinity

注意

请记住,如果底层操作系统 connect() 调用返回超时,即使指定了更大的 Timeout(例如 infinity),gen_tcp:connect 也会返回超时(即 {error, etimedout})。

注意

connect 指定的选项的默认值可能会受到内核配置参数 inet_default_connect_options 的影响。有关详细信息,请参阅 inet

链接到此函数

controlling_process(Socket, Pid)

查看源代码
-spec controlling_process(Socket, Pid) -> ok | {error, Reason}
                             when
                                 Socket :: socket(),
                                 Pid :: pid(),
                                 Reason :: closed | not_owner | badarg | inet:posix().

更改套接字的控制进程(所有者)。

将新的控制进程 Pid 分配给 Socket。控制进程是套接字向其发送消息的进程。如果此函数是由当前控制进程以外的任何其他进程调用的,则返回 {error, not_owner}

如果 Pid 标识的进程不是现有的本地 pid/0,则返回 {error, badarg}。在某些情况下,当 Socket 在此函数执行期间关闭时,也可能返回 {error, badarg}

如果套接字处于活动模式,则此函数会将调用者的邮箱中来自套接字的任何消息传输到新的控制进程。

如果在传输期间有任何其他进程与套接字交互,则可能无法正常工作,并且消息可能会保留在调用者的邮箱中。例如,在传输期间更改套接字的活动模式可能会导致这种情况。

-spec listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}
                when
                    Port :: inet:port_number(),
                    Options :: [inet:inet_backend() | listen_option()],
                    ListenSocket :: socket(),
                    Reason :: system_limit | inet:posix().

创建监听套接字。

创建一个套接字,并将其设置为侦听本地主机上的端口 Port

如果 Port == 0,则底层操作系统会分配一个可用的(临时)端口号,请使用 inet:port/1 来检索它。

以下选项可用

  • list - 收到的 Packet 以字节列表的形式传递,[byte/0]

  • binary - 收到的 Packetbinary/0 的形式传递。

  • {backlog, B} - B :: non_neg_integer/0。积压值定义了挂起连接队列可以增长到的最大长度。默认为 5

  • inet6 - 为 IPv6 设置套接字。

  • inet - 为 IPv4 设置套接字。

  • {fd, Fd} - 如果套接字在没有使用 gen_tcp 的情况下被创建,则使用此选项传递其文件描述符。

  • {ip, Address} - 如果主机有多个 IP 地址,此选项指定要侦听的 IP 地址。

  • {port, Port} - 指定要使用的本地端口号。

  • {ifaddr, Address} - 与 {ip, Address} 相同。

    但是,如果它是一个 socket:sockaddr_in/0socket:sockaddr_in6/0,则此选项优先于之前使用 ipport 选项设置的任何值。如果这些选项(ip 或/和 port)出现在此选项之后,则它们可以用于更新此选项的相应字段(对于 ip,更新 addr 字段,对于 port,更新 port 字段)。

  • {tcp_module, module()} - 覆盖要使用的回调模块。IPv4 默认为 inet_tcp,IPv6 默认为 inet6_tcp

  • option/0 - 请参阅 inet:setopts/2

在调用 accept/1,2 以接受传入的连接请求时,应使用返回的套接字 ListenSocket

注意

listen 指定的选项的默认值可能会受到内核配置参数 inet_default_listen_options 的影响。有关详细信息,请参阅 inet

-spec recv(Socket, Length) -> {ok, Packet} | {error, Reason}
              when
                  Socket :: socket(),
                  Length :: non_neg_integer(),
                  Packet :: string() | binary() | HttpPacket,
                  Reason :: closed | inet:posix(),
                  HttpPacket :: term().

等效于 recv(Socket, Length, infinity)

链接到此函数

recv(Socket, Length, Timeout)

查看源代码
-spec recv(Socket, Length, Timeout) -> {ok, Packet} | {error, Reason}
              when
                  Socket :: socket(),
                  Length :: non_neg_integer(),
                  Timeout :: timeout(),
                  Packet :: string() | binary() | HttpPacket,
                  Reason :: closed | timeout | inet:posix(),
                  HttpPacket :: term().

被动模式下的套接字接收数据包。

关闭的套接字由返回值 {error, closed} 指示。如果套接字未处于被动模式,则返回值为 {error, einval}

仅当套接字处于 raw 模式时,参数 Length 才具有意义,并且表示要读取的字节数。如果 Length0,则返回所有可用的字节。如果 Length > 0,则返回正好 Length 个字节,或者返回错误;除非套接字从另一侧关闭,然后返回 {error, closed} 之前的最后一次读取可能会返回少于 Length 个字节的数据。

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

任何进程都可以从被动套接字接收数据,即使该进程不是该套接字的控制进程。但是,在任何给定时间,只有一个进程可以对套接字调用此函数。不建议同时调用 recv,因为该行为取决于套接字实现,并且可能会返回诸如 {error, ealready} 之类的错误。

-spec send(Socket, Packet) -> ok | {error, Reason}
              when
                  Socket :: socket(),
                  Packet :: iodata(),
                  Reason :: closed | {timeout, RestData} | inet:posix(),
                  RestData :: binary() | erlang:iovec().

在套接字上发送数据包。

没有带有超时选项的 send/2 调用;如果需要超时,请使用套接字选项 send_timeout。请参阅“示例”部分。

只有当 inet_backend = socket 时,才能返回返回值 {error, {timeout, RestData}}

注意

非阻塞发送。

如果用户尝试发送的数据量超过了操作系统发送缓冲区中的可用空间,则“剩余数据”将存储在(inet 驱动程序)内部缓冲区中,并在后台发送。该函数立即返回 ok(通知调用者某些日期尚未发送)。在发送“剩余数据”时出现的任何问题可能会在稍后返回。

当使用 inet_backend = socket 时,行为是不同的。没有缓冲,而是调用者将“挂起”,直到所有数据都已发送,或者发送超时(由 send_timeout 选项指定)到期(即使在使用 inet 后端时,如果内部缓冲区已满,该函数也可能“挂起”)。

如果在使用 packet =/= raw 时发生这种情况,则已写入部分数据包。因此,此时不能写入新数据包,因为对等方无法将其与当前数据包中的数据区分开。相反,请将数据包设置为 raw,发送剩余数据(作为 raw 数据),然后再次将数据包设置为正确的数据包类型。

-spec shutdown(Socket, How) -> ok | {error, Reason}
                  when Socket :: socket(), How :: read | write | read_write, Reason :: inet:posix().

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

How == write 表示关闭套接字的写入,仍然可以从中读取。

如果 How == read 或者 Socket 端口中没有缓冲的传出数据,则立即执行关闭,并且在 Reason 中返回遇到的任何错误。

如果套接字端口中有数据缓冲,则在将缓冲的数据写入操作系统协议栈之前,不会对套接字执行关闭。如果遇到任何错误,则会关闭套接字,并且下一个 recv/2send/2 调用将返回 {error, closed}

如果对等方执行其写入端的关闭,则选项 {exit_on_close, false} 很有用。然后,在接收指示套接字已关闭后,套接字仍保持打开状态以进行写入。

注意

异步关闭写入 (How :: write | read_write)。

如果在 inet 驱动程序在后台发送缓冲数据时尝试关闭,则关闭将推迟到发送完所有缓冲数据为止。此函数立即返回 ok,并且通知调用者(关闭已被推迟)。

当使用 inet_backend = socket 时,行为是不同的。使用 How :: write | read_write 进行关闭将始终立即执行。