UDP的概念:
Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。
Internet 的传输层有两个主要协议,互为补充。无连接的是 UDP,它除了给应用程序发送数据包功能并允许它们在所需的层次上架构自己的协议之外,几乎没有做什么特别的事情。面向连接的是 TCP,该协议几乎做了所有的事情。
最简单的UDP服务器
server(Port) ->
{ok, Socket} = gen_udp:open(Port, [binary]),
loop(Socket).
loop(Socket) ->
receive
{udp, Socket, Host, Port, Bin} ->
BinReply = 1,
gen_udp:send(Socket, Host, Port, BinReply),
loop(Socket)
end.
UDP比TCP程序要简单,因为无需担心如何让进程接收“套接字关闭”的消息。我们用binary模式打开了套接字,它告诉驱动要把所有消息以二进制数据的形式发送给控制进程。
这里有一个非常简单的客户端。它只是打开一个UDP套接字,向服务器发 送一个消息,等待回复(或者超时),然后关闭套接字并返回服务器的返回值。
client(Request) ->
{ok, Socket} = gen_udp:open(0, [binary]),
ok = gen_udp:send(Socket, "locahost", 4000, Request),
Value = receive
{udp, Socket, _, _, Bin} ->
{ok, Bin}
after 2000 ->
error
end,
gen_udp:close(Socket),
Value.
必须设置一个超时时间,因为UDP是不可靠的,可能会收不到回复
一个简单的UDP阶乘服务器
服务器代码
start_serer () ->
spawn(fun() ->server(4000) end).
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).
客户端代码
client(N) ->
{ok, Socket} = gen_udp:open(0, [binary]),
io:format("cliemt 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 20000 -> 0
end,
gen_udp:close(Socket),
Value.
运行结果
服务端
客户端求一个20的阶乘
拿到结果.
注意
因为UDP是一种无连接协议,所以服务器无法通过拒绝读取来自某个客户端的数据来阻挡它。服务器对谁是客户端一无所知。
大型UDP数据包可能会分段通过网络。当UDP数据经过网络上的路由器时,如果数据大小超 过了路由器允许的最大传输单元(Maximum Transfer Unit,简称MTU)大小,分段就会发生。
通常的建议是在调整UDP网络时从一个较小的数据包大小开始(比如大约500字节),然后逐步增大 并测量吞吐量。如果吞吐量在某个时刻骤减,你就知道数据包太大了。
UDP数据包可以传输两次(这出乎一些人的意料之外),所以在编写远程过程调用代码时一 定要小心。第二次查询得到的回复可能只是第一次查询回复的复制。
为防止这类问题,可以修改 客户端代码来加入一个唯一的引用,然后检查服务器是否返回了这个引用。要生成一个唯一的引 用,需要调用Erlang的内置函数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;
{_Some, _} ->
wait_for_ref(Socket, Ref)
end
after 1000 ->
14
end.