套接字连接队列解析
listen():监听端口,用在 TCP连接 中的 服务器端 角色;
listen()函数调用格式: int listen(int sockfd, int backlog);
要理解好backlog这个参数,我们需要先谈一谈 “监听套接字 队列”的话题;
监听套接字的队列
对于一个调用listen()进行监听的套接字,操作系统会给这个套接字 维护两个队列;
a)未完成连接队列 【保存连接用的】
当客户端 发送tcp连接三次握手的第一次【syn包】给服务器的时候,服务器就会在未完成队列中创建一个跟这个 syn包对应的一项,其实,我们可以把这项看成是一个半连接【因为连接还没建立起来呢】,这个半连接的状态会从LISTEN变成SYN_RCVD状态,同时给客户端返回第二次握手包【syn,ack】。这个时候,其实服务器是在等待完成第三次握手。
b)已完成连接队列 【保存连接用的】
当第三次握手完成了,这个连接就变成了ESTABLISHED状态,每个已经完成三次握手的客户端 都放在这个队列中作为一项;
backlog
backlog曾经的含义:已完成队列和未完成队列二者条目之和不能超过 backlog;
(1)客户端这个connect()什么时候返回,其实是收到三次握手的第二次握手包(也就是收到服务器发回来的syn/ack)之后就返回了;
(2)RTT是未完成队列中任意一项在未完成队列中留存的时间,即未完成队列移到已完成队列的时间,这个时间取决于客户端和服务器;
对于客户端,这个RTT时间是第一次和第二次握手加起来的时间;
对于服务器,这个RTT时间实际上是第二次和第三次握手加起来的时间;
如果这三次握手包传递速度特别快的话,大概187毫秒能够建立起来这个连接;这个时间挺慢,所以感觉建立TCP连接的成本挺高;【短连接游戏-每一次发送数据包建立TCP连接】
(3)如果一个恶意客户,迟迟不发送三次握手的第三个包。那么这个连接就建立不起来,那么这个处于SYN_RCVD的这一项【服务器端的未完成队列中】,就会一致停留在服务器的未完成队列中,这个停留时间大概是75秒,如果超过这个时间,这一项会被操作系统干掉;
accept()函数
accept()函数,就使用来 从 已完成连接队列 中 的队首【队头】位置取出来一项【每一项都是一个已经完成三路握手的TCP连接】,返回给进程;
1 | connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); |
注意:这里返回的是一个新的socket——connfd,后续本服务器就用connfd和客户端之间收发数据,而原有的lisenfd依旧用于继续监听其他连接
如果已完成连接队列是空的呢?
那么咱们这个范例中accept()会一致卡在这里【休眠】等待,一直到已完成队列中有一项时才会被唤醒;所以,从编程角度,我们要尽快的用accept()把已完成队列中的数据【TCP连接】取走,大家必须有这个认识;
accept()返回的是个套接字,这个套接字就代表那个之前已经用三次握手建立起来的那个tcp连接,因为accept()是从 已完成队列中取的数据;
换句话来说,我们服务器程序,必须要严格区分两个套接字:
a)监听9000端口这个套接字,这个东西叫“监听套接字【listenfd】”,只要服务器程序在运行,这个套接字就应该一直存在;
b)当客户端连接进来,操作系统会为每个成功建立三次握手的客户端再创建一个套接字【当然是一个已经连接套接字】,accept()返回的connfd就是这种套接字;也就是从已完成连接队列中取得的一项。随后,服务器使用这个accept()返回的套接字和客户端通信的;
思考题:
(1)如果两个队列之和【已完成连接队列,和未完成连接队列】达到了listen()所指定的第二参数,也就是说队列满了;此时,再有一个客户发送syn请求,服务器怎么反应?
实际上服务器会忽略这个syn,不给回应; 客户端这边,发现syn没回应,过一会会重发syn包;
(2)从连接被扔到已经完成队列中去,到accept()从已完成队列中把这个连接取出这个之间是有个时间差的,如果还没等accept()从已完成队列中把这个连接取走的时候,
客户端如果发送来数据,这个数据就会被保存再已经连接的套接字的接收缓冲区里,这个缓冲区有多大,最大就能接收多少数据量;
SYN攻击[syn flood]
拒绝服务攻击(DOS/DDOS):不停地向服务器发送SYN请求,队列满了后,合法的用户将无法正常连接。
backlog:曾经的含义:已完成队列和未完成队列二者条目之和不能超过 backlog;
backlog:进一步明确和规定了:指定给定套接字上内核为之排队的最大已完成连接数【已完成连接队列中最大条目数】;
大家在写代码时尽快用accept()把已完成队列里边的连接取走,尽快 留出空闲为止给后续的已完成三路握手的条目用,那么这个已完成队列一般不会满;一般这个backlog值给300左右;