erlang-gen_tcp手册(翻译)

Gen_tcp

         Gen_tcp模块遵循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}

            | {bit8, clear | set |on | off}

            | {buffer, integer()>= 0}

            | {delay_send,boolean()}

            | {deliver, port | term}

            | {dontroute, boolean()}

            | {exit_on_close,boolean()}

            | {header, integer()>= 0}

            | {high_watermark, integer()>= 0}

            | {keepalive, boolean()}

            | {linger, {boolean(),integer() >= 0}}

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

option_name() = active

            | bit8

            | buffer

            | delay_send

            | deliver

            | dontroute

            | exit_on_close

            | header

            | high_watermark

            | keepalive

            | linger

            | 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

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}

类型:

    Address = inet:ip_address() |inet:hostname()

    Port = inet:port_number()

    Options = [connect_option()]

    Timeout = timeout()

    Socket = socket()

    Reason = inet:posix()

    connect通过TCP端口Port和主机IP地址Address连接到服务器。参数Address可以是主机名字或者主机IP地址。

    可用的选项:

    list

        数据包作为列表传输。

    binary

        数据包作为二进制数据传输。

    {ip,ip_address()}

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

    {port,Port}

        指定使用哪一个本地端口。

    {fd,integer()>=0}

        如果套接字没有使用gen_tcp建立连接,则通过这个选项给它传递文件描述符。

    Inet6

        使用IPv6建立套接字。

    Inet

        使用IPv4建立套接字。

    Opt

        参考inet:setopts/2

    使用send/2向connect返回的Socket发送数据包。数据包的发送如同消息的传递

{tcp, Socket, Data}

    如果socket被关闭,将会发送下面的消息:

{tcp_closed,Socket}

    如果socket出现错误,将会发送下面的消息:

{tcp_error,Socket,Reason}

    如果在socket的选项列表中指定{active,false},这样就得使用recv/2接收数据包。

    选项Timeout指定了一个毫秒级的超时,默认值是infinity。

注意:

    内核配置参数inet_default_connect_options可能影响到connect选项的默认值。详情请参考inet(3)。

 

listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}

类型:

    Port = inet:port_number()

    Options = [listen_option()]

    ListenSocket = socket()

    Reason = inet:posix()

    建立一个socket在本地主机的Port端口上监听连接。

    如果Port==0,底层操作系统将通过inet:port/1重新分配一个可用的端口给它。

可用的选项:

    list

        数据包作为列表传输。

    binary

        数据包作为二进制数据传输。

    {backlog,B}

        B是一个大于等于0的整数,默认值是5。B的值定义了等待连接的队列的最大长度。

    {ip,ip_address()}

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

    {port,Port}

        指定使用哪一个本地端口。

    {fd,Fd}

        如果套接字没有使用gen_tcp建立连接,则通过这个选项给它传递文件描述符。

    Inet6

        使用IPv6建立套接字。

    Inet

        使用IPv4建立套接字。

    Opt

        参考inet:setopts/2

    返回的socket标示符ListenSocket只能用于函数accept/1,2。

注意:

    内核配置参数inet_default_listen_options可能影响listen选项的默认值,详情请参考inet(3)。

 

accept(ListenSocket) -> {ok, Socket} | {error, Reason}

accept(ListenSocket, Timeout) -> {ok, Socket} | {error, Reason}

类型:

    ListenSocket = socket()

    Returned by listen/2.

    Timeout = timeout()

    Socket = socket()

    Reason = closed | timeout |inet:posix()

    通过socket监听接受到来的连接请求。ListenSocket必须是由listen/2返回的。Timeout指定了一个毫秒级的超时,默认为infinity。

    如果连接建立则返回{ok,Socket},如果ListenSocket关闭了则返回{error,closed},如果在指定的时间内没有建立连接,则返回{error,timeout}。如果某些东西出错,也可能返回一个POSIX错误。可能的错误值请参考inet(3)。

    使用send/2向accept返回的Socket发送数据包。数据包的发送如同消息的传递:

{tcp, Socket, Data}

    如果在socket的选项列表中指定{active,false},这样就得使用recv/2接收数据包。

注意:

    值得注意的是accept调用不必从socket进程传出,在5.5.3或更高版本中,许多同步接受调用从不同的进程传出,这是一个处理到来的连接请求的进程池。

send(Socket, Packet) -> ok | {error, Reason}

类型:

    Socket = socket()

    Packet = iodata()

    Reason = inet:posix()

    通过socket发送数据。

    Send调用没有超时选项,如果需要超时,则使用send_timeout选项。具体参考examples部分。

 

recv(Socket, Length) -> {ok, Packet} | {error, Reason}

recv(Socket, Length, Timeout) -> {ok, Packet} | {error, Reason}

类型:

    Socket = socket()

    Length = integer() >= 0

    Timeout = timeout()

    Packet = string() | binary() |HttpPacket

    Reason = closed | inet:posix()

    HttpPacket = term()

    HttpPacket的描述请参考erlang:decode_packet/3。

    这个函数以被动方式接收数据包。返回值{error,closed}表明socket已关闭。

    当socket是raw模式时参数Length是唯一有意义的,并且Length表明接收字节的数量。如果Length=0,所有有效的字节都会被接收。如果Length>0,则只会接收Length长度的字节或者发生错误;当socket从另一端关闭,接收的数据长度可能会小于Length。

    选项Timeout指定了一个毫秒级的超时,默认值是infinity。

 

controlling_process(Socket, Pid) -> ok | {error, Reason}

类型:

    Socket = socket()

    Pid = pid()

    Reason = closed | not_owner |inet:posix()

    为Socket分配一个新的控制进程Pid。控制进程就是从socket接收数据的进程。如果被控制进程以外的进程调用则会返回{error,eperm}。

 

close(Socket) -> ok

类型:

    Socket = socket()

关闭TCP套接字。

 

shutdown(Socket, How) -> ok | {error, Reason}

类型:

    Socket = socket()

    How = read | write | read_write

    Reason = inet:posix()

    单向或双向的关闭socket。

    How == write意味着关闭socket的写通道,读通道仍然可用。

    为了能够通过写的一端处理相同的关闭,选项{exit_on_close,false}将会很有用。

Examples

         下面这个例子阐述了选项{active,once}和使用单一的监听函数接受多个连接请求建立多个工作进程的socket用法。函数start/2得到工作进程的数量和监听连接请求的端口号。如果LPort指定为0,将使用一个暂时的端口号,所以函数start返回一个真实分配的端口号。

 

start(Num,LPort) ->

         casegen_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) ->

         casegen_tcp:accept(LS) of

                   {ok,S}->

                            loop(S),

                            server(LS);

                   Other->

                            io:format("acceptreturned ~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选项,因为关于send的超时通过socket选项send_timeout来处理的。没有接收的send操作的等级非常高,这是由底层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中请求被处理,我们可以检测发送超时:

 

loop(Sock) ->

         receive

                   {Client,send_data, Binary} ->

                            casegen_tcp:send(Sock,[Binary]) of

                                     {error,timeout} ->

                                               io:format("Sendtimeout, 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("Someother 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迟早会有用的。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值