链接建立的三个步骤
- listen一个port,获得socket
- accept这个socket,等待链接建立生成asocket
- recv接收asocket,读取里面的内容
1、Listen端口
端口listen的时候,内核会维护2个队列,一个队列存放未完成的连接,一个队列存放已完成的连接。这里的未完成的连接指的是客户端第一次握手包被服务器收到的连接,已完成的连接指的是客户端第三次握手包被服务器收到的连接。
2、Accept链接
1)accept的时候,会从已完成的队列获取连接。
- 如果这是一个阻塞的accept,则在已完成队列是空的情况下会一直等待,直到有连接完成,返回该连接的socket。
- 如果这是一个非阻塞的accept,则在空的情况下,会马上返回ewouldblock的错误。
2)异步accpet实现
通常是链接完成通知accept接收(但此时如果服务器完成链接,accept被其他调用没有收到这个链接,客户端刚好来rst消息,unix发现链接没有处理会中断)。
3)Erlang实现的async_accept
erlang的gen_tcp:accept的实现是基于prim_inet:async_accept的,而prim_inet:async_accept是异步的方式。
所以,erlang底层关于prim_inet:async_accept的实现应该就是上面说的 nonblocking accept + select/poll/epoll 的方式。
3、如何更高效的accpet
单个进程使用async_accept并不能提高这种情况的处理效率。其实这个情况我们可以创建多个进程同时accept socket。这个方案也是很常见的,ranch(erlang的网络accept库)就是使用了这一方案。ranch的做法大概是由ranch_acceptor_sup开始gen_tcp:listen,并把得到的listensokcet传递给多个子进程ranch_acceptor,然后每个ranch_acceptor都开始阻塞在gen_tcp:accept(listensocket, Timeout)上,然后在同时大量连接到来的时候,这些进程会有序地一个个accept到socket。
(用ranch做了一个测试,当连接一个个到来的时候,发现ranch_acceptor进程是很有顺序地一个个被调用的然后返回socket,可见内部的调度算法还是很合理的,而且也预料到会有多进程调用这种情况的出现)