Listen函数的原型是:int listen(int socket, int backlog);此函数用于建立tcp连接时监听系统的某一个端口。
第一个参数比较好理解,是调用listen之前创建的socket的句柄,第二个参数单从名称不好理解,从linux的man资料来看的解释是tcp半连接队列的大小,至于何为半连接队列则没有做进一步说明,这里详细解释一下第二个参数backlog的含义。
说到backlog,这里先从tcp的三次握手开始说起,下图是tcp三次握手的过程图:
三次握手过程:
1. 客户端向服务器发送一个SYN置位的TCP报文,其中包含连接的初始序列号x和一个窗口大小(表示客户端上用来存储从服务器发送来的传入段的缓冲区的大小)。
2. 服务器收到客户端发送过来的SYN报文后,向客户端发送一个SYN和ACK都置位的TCP报文,其中包含它选择的初始序列号y、对客户端的序列号的确认x+1和一个窗口大小(表示服务器上用来存储从客户端发送来的传入段的缓冲区的大小)。
3. .客户端接收到服务器端返回的SYN+ACK报文后,向服务器端返回一个确认号y+1和序号x+1的ACK报文,一个标准的TCP连接完成。
从上图可以看出右边服务器的有两个状态,第一个状态是收到客户端SYNC包的状态“SYN RECD”
第二个状态是发送了SYNC + ACK之后收到客户端ACK响应包的状态”ESTABLISHED”
这两个状态是TCP连接真正建立之前的两个过度状态,当服务器同时接收来自多个客户端的连接请求时,系统会同时存在多个这样的状态,系统为了维护这些状态,需要开辟两个队列来存放这两个中间状态,一个叫“半连接队列”用来存放“SYN RECVD”状态的连接,一个叫“完整连接队列”用来存放“ESTABLISHED”状态的tcp连接。
当处于“SYN RECVD”队列的连接,收到了客户端的ACK响应包的时候,这个连接会从“SYN RECVD”队列删除,并添加到”ESTABLISHED“队列的末尾,这两个队列大小的总和就是listen函数的backlog第二个参数来决定的。当你调用listen函数之后调用accept函数,其实是从“完整连接队列”里面取一个连接出来建立真正的tcp连接。
两个队列的总大小(backlog)在linux系统是有限制的,具体可以看cat /proc/sys/net/core/somaxconn,之前默认是128,现在系统运维的同学都已经统一做了修改,现在最大为2048,这个参数在系统内核是有做限制的,假如你设置的backlog小于等于0,系统会默认按照本身定义的最小的值来设置,假如你设置的backlog大于somaxconn定义的大小,系统会默认按照somaxconn的大小来分配半连接队列。当然了,你在启动程序之前也可以先修改一下这个系统限制:
#set somaxconn = 4096
echo 4096 > /proc/sys/net/core/somaxconn
题外话:
大家经常听到的DDOS攻击,就是通过控制大量的“僵尸“机器发送大量的tcp请求到目标服务器,但是在tcp握手的最后一步,不发送对来自服务器SYNC数据包的ACK响应包,导致大量的无效请求占满了”SYN RECVD”队列,从而导致真正有效的用户尝试建立tcp连接到服务器时,由于没有空闲的”SYN RECVD”队列的节点而无法建立成功。