int listen(int sockfd,int backlog)
做两件事情:
1、socket创建时,默认为一个主动套接字——将用connect发起连接的客户端套接字,listen将其转换为被动套接字,从CLOSED状态变为LISTEN状态
2、第二个参数,规定了内核为相应套接字排队的最大连接个数;
内核为任何一个监听套接字维护两个队列:
1、未完成连接队列,以下每个SYN分节对应队列中的一项:已由某个客户端发出并到达服务器,服务器正在等待TCP三次握手过程。这些套接字处于SYN_RCVD状态;
2、已完成连接队列:每个已完成TCP三次握手的套接字对应其中一项。这些套接字处于ESTABLISHED状态;等待accept取走;
两个队列满时,系统不一样,实现也不太一样,后面测试了aix和linux下的实现。
未完成队列中的连接,完成三次握手后,会移入已完成队列最后,等待程序accept,如果已完成队列已满,会等待空闲出来(这时netstat查看端口状态会有SYN_RCVD)。
在AIX中,已完成队列满的话,会忽略外部的SYN请求,外围会收到和这个端口没有程序绑定一样的错误,ECONNREFUSED,这样是为了让客户端重复尝试是否有空闲出来的,而不是立即返回错误。
在LINUX中,已完成队列满,未完成没有满,会忽略第三次握手的客户端返回的ACK节(也就是认为客户端没有收到第二次握手的SYN+ACK,会一直重发这个包),这时客户端会显示为ESTABLISHED状态,connect函数会返回,但会应答ACK节。服务端显示为SYN_RCVD状态(直到未完成队列也满,这时会忽略SYN请求),若未完成队列中的SYN_RCVD一直等待到超时还没有进入已完成队列,则丢弃连接(客户端不知道,只有在read时才知道)
backlog曾经决定未完成和已完成队列之和,现在根据系统不一样实现不太一样,LINUX2.2以后,特指已完成队列(不是直接指定,有个转换,依赖具体实现),这个队列最大值由/proc/sys/net/core/somaxconn决定,默认是最大是128。
未完成队列由/proc/sys/net/ipv4/tcp_max_syn_backlog决定,大多默认是512。这个设置有效的前提是系统的syncookies功能被禁用,如果系统的syncookies功能被启用,那么这个设置是无效的。Syncookies是在内核编译的时候设置的,查看syncookies是否启动:
cat /proc/sys/net/ipv4/tcp_syncookies
如果是“1”说明已启用,为“0”说明未启用。
那么为syncookies是做什么的呢,为什么它会和未完成队列有关系。简单的说它是为防范SYN Flood攻击的设计。
不同内核的系统,对已连接队列满的情况,处理方法不一样,测试使用:
aix5.3:会将后续的SYN请求忽略,客户端的TCP会重发SYN请求(这个不用进程控制),看是否有空闲,超时后,会返回ECONNREFUSED错误
csibma01:/data02/home/wangfeng> tcpdump host 20.26.11.32 and 10.70.42.84
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on en9, link-type 1, capture size 96 bytes
14:40:39.180589 IP 10.70.42.84.63268 > csibma01.ddi-tcp-1: S 3834870973:3834870973(0) win 65535 <mss 1460>
14:40:41.957463 IP 10.70.42.84.63268 > csibma01.ddi-tcp-1: S 3834870973:3834870973(0) win 65535 <mss 1460>
14:40:48.040971 IP 10.70.42.84.63268 > csibma01.ddi-tcp-1: S 3834870973:3834870973(0) win 65535 <mss 1460>
14:41:00.217172 IP 10.70.42.84.63268 > csibma01.ddi-tcp-1: S 3834870973:3834870973(0) win 65535 <mss 1460>
14:41:24.536922 IP 10.70.42.84.63268 > csibma01.ddi-tcp-1: S 3834870973:3834870973(0) win 65535 <mss 1460>
linux 2.6.32:会忽略第三次握手的客户端返回的ACK节(也就是认为客户端没有收到第二次握手的SYN+ACK,会一直重发这个包),这时客户端会显示为ESTABLISHED状态,但会应答ACK节,connect函数会返回。服务端显示为SYN_RCVD状态(直到未完成队列也满,这时会忽略SYN请求),若未完成队列中的SYN_RCVD一直等待到超时还没有进入已完成队列,则丢弃连接(客户端不知道,只有在read时才知道)
15:04:48.293738 IP pc-csapp06.ddi-tcp-1 > 20.26.11.32.38481: S
2607266952:2607266952(0) ack
541251722 win 5792 <mss 1460,nop,nop,timestamp 2037871124 1441562214,nop,wscale 7>
15:04:48.293825 IP 20.26.11.32.38481 > pc-csapp06.ddi-tcp-1: . ack 1 win 32580 <nop,nop,timestamp 1441562215 2037871124>
15:04:51.904682 IP pc-csapp06.ddi-tcp-1 > 20.26.11.32.38481: S
2607266952:2607266952(0) ack
541251722 win 5792 <mss 1460,nop,nop,timestamp 2037872027 1441562215,nop,wscale 7>
15:04:51.904817 IP 20.26.11.32.38481 > pc-csapp06.ddi-tcp-1: . ack 1 win 32580 <nop,nop,timestamp 1441562222 2037872027>