gen_tcp:send的深度解刨和使用指南(初稿)
原创文章,转载请注明: 转载自系统技术非业余研究
本文链接地址: gen_tcp:send的深度解刨和使用指南(初稿)
在大家的印象中, gen_tcp:send是个很朴素的函数, 一调用数据就喀嚓喀嚓到了对端. 这是个很大的误解, Erlang的otp文档写的很不清楚. 而且这个功能对于大部分的网络程序是至关重要的, 它的使用对否极大了影响了应用的性能. 我听到很多同学在抱怨erlang的性能低或者出了很奇怪的问题, 很多是由于对系统的不了解, 误用的. 我下面就来解刨下, 文章很长, 而且需要读者熟悉erlang和底层的知识, 跟我来吧.
这篇文章是基于Erlang R13B04这个版本写的.
以下是从gen_tcp文档中摘抄的:
gen_tcp:send(Socket, Packet) -> ok | {error, Reason}
* Socket = socket()
* Packet =[char()] | binary()
* Reason = posix()
* Sends a packet on a socket.There is no send call with timeout option, you use the send_timeout socket option if timeouts are desired. See the examples section.
典型的使用如下:
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.
很简单是把? 乍一看确实很简单, 但是这是迷惑人的开始.
我们上源代码:
lib/kernel/src/gen_tcp.erl
124send(S, Packet) when is_port(S) -> %这里可以看出 S是个port 125 case inet_db:lookup_socket(S) of 126 {ok, Mod} -> %Mod可能是inet_tcp.erl 或者 inet6_tcp.erl 127 Mod:send(S, Packet); 128 Error -> 129 Error 130 end.
lib/kernel/src/inet_tcp.erl
49send(Socket, Packet, Opts) -> prim_inet:send(Socket, Packet, Opts). %转给prim_inet模块 50send(Socket, Packet) -> prim_inet:send(Socket, Packet, []).
erts/preloaded/src/prim_inet.erl
360send(S, Data, OptList) when is_port(S), is_list(OptList) -> 361 ?DBG_FORMAT("prim_inet:send(~p, ~p)~n", [S,Data]), 362 try erlang:port_command(S, Data, OptList) of <strong>%推给底层的port模块来处理</strong> 363 false -> % Port busy and nosuspend option passed 364 ?DBG_FORMAT("prim_inet:send() -> {error,busy}~n", []), 365 {error,busy}; 366 true -> <strong>% Port模块接受数据</strong> 367 receive 368 {inet_reply,S,Status} -> <strong>%阻塞, 等待回应</strong> 369 ?DBG_FORMAT("prim_inet:send() -> ~p~n", [Status]), 370 Status 371 end 372 catch 373 error:_Error -> 374 ?DBG_FORMAT("prim_inet:send() -> {error,einval}~n", []), 375 {error,einval} 376 end. 377 378send(S, Data) -> 379 send(S, Data, []).
从上面这几段代码我们可以看出,当我们调用gen_tcp:send的时候, kernel模块会根据gen_tcp socket的类型决定调用相应的模块. 这个模块要么是inet_tcp, 要么是inet6_tcp. 这个模块会把发送请求委托给
prim_inet模块. prim_inet模块首先检查Socket是否合法, 如果合法然后调用erlang:port_command把系统推到ERTS运行期.
这个推的结果有2个: 1. 成功, 进程挂起等待运行期的反馈. 2. 失败,立即返回.
什么情况下会失败呢?
1. 驱动不支持soft_busy, 但是我们用了force标志
2. 驱动已经busy了, 但是我们不允许进程挂起.
Read more…
Post Footer automatically generated by wp-posturl plugin for wordpress.
Recent Comments