zoukankan      html  css  js  c++  java
  • Socket的UDP协议在erlang中的实现

    现在我们看看UDP协议(User Datagram Protocol,用户数据报协议)。使用UDP,互联网上的机器之间可以互相发送小段的数据,叫做数据报。UDP数据报是不可靠的,这意味着如果客户端发送一系列的UDP数据报到服务器,收到的数据报顺序可能是错误的。不过收到的数据报肯定是正确的。大的数据报会被分为多个小的分片,IP协议负责重新组装这些分片,并最终交付给应用。

    UDP是无连接的协议,这意味着客户端无需连接服务器即可发送消息。这也意味着程序更加适于大量客户端收发小的消息报文。

    在Erlang中编写UDP客户端和服务器比TCP时更简单,因为我们无需管理连接。

    1   简单的UDP服务器和客户端

    首先,我们看看服务器,一个通用的服务器样式如下:

     1 server(Port) ->
     2     {ok,Socket} = gen_udp:open(Port,[binary]),
     3     loop(Socket).
     4 
     5 loop(Socket) ->
     6     receive
     7         {udp,Socket,Host,Port,Bin} ->
     8             BinReply = ... ,
     9             gen_udp:send(Socket,Host,Port,BinReply),
    10             loop(Socket)
    11     end.
    View Code

    这里比TCP协议的例子更简单,因为我们至少不需要关心连接关闭的消息。注意我们以二进制方式打开socket,驱动也会以二进制数据的形式将报文发送到应用。

    注意客户端。这里有个简单的客户端。它仅仅打开UDP socket,发送消息到服务器,等待响应(或超时),然后关闭socket并返回从服务器接收到的值。

    client(Request) ->
        {ok,Socket} = gen_udp:open(0,[binary]),
        ok = gen_udp:send(Socket,"localhost",4000,Request),
        Value = receive
                    {udp,Socket,_,_,Bin} ->
                        {ok,Bin}
                    after 2000 ->
                        error
                    end,
        gen_udp:close(Socket),
        Value
    View Code

    我们必须拥有一个超时,否则UDP的不可靠会让我们永远得不到响应。

    2   一个UDP阶乘服务器

    我们可以很容易的构造一个UDP的阶乘服务器。代码模仿前一节。

    -module(upd_test).
    -export([start_server/0,client/1]).
    
    start_server() ->
        spawn(fun() -> server(4000) end).
    View Code
    %% 服务器
    server(Port) ->
        {ok,Socket}=gen_udp:open(Port,,[binary]),
        io:format("server opened socket:~p~n",[Socket]),
        loop(Socket).
    
    loop(Socket) ->
        receive
            {udp,Socket,Host,Port,Bin} =Msg ->
                io:format("server received:~p~n",[Msg]),
                N=binary_to_term(Bin),
                Fac=fac(N),
                gen_udp:send(Socket,Host,Port,term_to_binary(Fac)),
                loop(Socket)
        end.
    
    fac(0) -> 1;
    fac(N) -> N*fac(N-1).
    View Code
    %% 客户端
    client(N) ->
        {ok,Socket} = gen_upd:open(0,[binary]),
        io:format("client opened socket=~p~n",[Socket]),
        ok=gen_udp:send(Socket,"localhost",4000,term_to_binary(N)),
        Value=receive
            {udp,Socket,_,_,Bin}=Msg ->
                io:format("client received:~p~n",[Msg]),
                binary_to_term(Bin)
            after 2000 ->
                0
            end,
        gen_udp:close(Socket),
        Value
    View Code

    注意我增加了一些打印语句,所以我们可以看到程序执行的过程。我一般是开发阶段加很多打印语句,而在工作正常后就注释掉了。

    现在让我们运行例子,首先启动服务器:

    1> udp_test:start_server().
    server opened socket:#Port<0.106>
    <0.34.0>

    这会在后台运行,所以我们发出一个客户端请求:

    2> udp_test:client(40).
    client opened socket=#Port<0.105>
    server received:{udp,#Port<0.106>,{127,0,0,1},32785,<<131,97,40>>}
    client received:{udp,#Port<0.105>,
                      {127,0,0,1},4000,
                      <<131,110,20,0,0,0,0,0,64,37,5,255,
                        100,222,15,8,126,242,199,132,27,
                        232,234,142>>}
    815915283247897734345611269596115894272000000000

    3   UDP的附加注释

    我们必须注意的是UDP是无连接的协议,也就四海服务器无法拒绝客户端发送数据,甚至不知道客户端是谁。

    大个的UDP报文会被切分成多个分片分别在网络上传输。分片发生在数据报长度大于最大传输单元(MTU)时,以确保通过路由器等网络设备以后仍然可以到达。一般的测量方法是开始于一个足够小的包(比如500字节),然后逐渐增加,直到发现MTU为止。如果在某一点发现数据报被丢弃了,那么,你就直到可以传输的最大报文长度了。

    一个UDP数据报可以被传输两次,所以你必须小心的编码以防备这个事。因为他可能会对同一个请求的第二次出现而再做一次响应。想要防止,我们可以修改客户端代码来在每个请求中加一个唯一引用,并且检查响应中的这个唯一引用。想要生成一个唯一引用,我们可以用Erlang BIF的 make_ref ,就会生成一个全局唯一引用。远程过程调用现在可以这样写:

    client(Request) ->
        {ok,Socket} = gen_udp:open(0,[binary]),
        Ref=make_ref(),
        B1=term_to_binary({Ref,Request}),
        ok=gen_udp:send(Socket,"localhost",4000,B1),
        wait_for_ref(Socket,Ref).
    
    wait_for_ref(Socket,Ref) ->
        receive
            {udp,Socket,_,_,Bin} ->
                case binary_to_term(Bin) of
                    {Ref,Val} ->
                        Val;
                    {_SomeOtherRef,_} ->
                        wait_for_ref(Socket,Ref)
                end;
        after 1000 ->
            ...
        end.
    View Code

     ps:这里它相当于加上了个ref的唯一值,去检查clinet返回响应的做校验.

  • 相关阅读:
    ACM-ICPC 2018 徐州赛区网络预赛 I. Characters with Hash
    hdu 5437
    poj 1502
    ACM-ICPC 2018 沈阳赛区网络预赛 K. Supreme Number
    ACM-ICPC 2018 沈阳赛区网络预赛 F. Fantastic Graph
    ACM-ICPC 2018 南京赛区网络预赛 B. The writing on the wall
    ACM-ICPC 2018 南京赛区网络预赛 J. Sum
    法里数列
    ACM-ICPC 2018 南京赛区网络预赛 L. Magical Girl Haze
    Hashtable 为什么不叫 HashTable?
  • 原文地址:https://www.cnblogs.com/unqiang/p/4224141.html
Copyright © 2011-2022 走看看