恶意客户端慢TCP握手的影响及多线程服务器accept()作用分析
问题背景
我们搭建了如下简单的多进程TCP服务器:
int server_fd = socket(/* args */); setsockopt(server_fd, /* other args... */); bind(server_fd, /* other args... */); listen(server_fd, backlog); while (1) { int client_fd = accept(server_fd, /* other args... */); if (fork() == 0) { // handle client request close(client_fd); exit(0); } }
针对这类服务器,我们有以下疑问:
- 若恶意客户端极慢地完成初始TCP握手,服务器能否正常接受其他合法客户端?
- 阻塞式
accept()调用是否会被该慢客户端阻断? accept()的底层执行逻辑是什么?它仅从已就绪队列取出套接字并创建文件描述符,还是会主动与客户端完成握手?- 等待TCP握手的过程是否会影响其他入站连接?
核心解答
1. accept()的底层执行逻辑
accept()完全不参与TCP三次握手的过程,它的唯一工作是从内核维护的已完成三次握手的连接队列中取出一个连接,为该连接创建新的socket文件描述符并返回给用户态程序。TCP握手的全流程(客户端发SYN → 服务器回复SYN-ACK → 客户端回复ACK)是由内核自动处理的,和accept()调用没有任何关系。
2. 慢客户端不会阻塞accept()调用
恶意客户端慢完成握手,意味着它的连接停留在半连接队列(处于SYN_RCVD状态,只发送了SYN包,还没回复ACK完成三次握手)。这类连接根本不会进入accept()处理的已完成队列,所以无论有多少这样的慢客户端,都不会阻塞accept()——只要已完成队列里有合法客户端的连接,accept()就会立即返回;只有当已完成队列为空时,accept()才会阻塞等待新的完成握手的连接。
3. 慢客户端的潜在影响
慢客户端可能会占满半连接队列(队列大小由系统参数控制,比如Linux下的tcp_max_syn_backlog),这时候新的客户端SYN包会被内核丢弃,导致合法客户端无法发起握手。但这属于连接建立阶段的队列溢出问题,和accept()是否被阻塞是两码事——服务器的accept()依然会正常处理已完成队列里的连接,只是新的握手请求可能无法被响应。
4. 对其他合法客户端的影响
只要半连接队列没被占满,合法客户端的握手过程会被内核正常处理,完成后进入已完成队列,accept()就能顺利取出这些连接,服务器可以正常服务合法客户端。只有当慢客户端把半连接队列撑满时,才会影响新的握手请求,但已建立的连接和accept()的正常执行不受影响。
内容的提问来源于stack exchange,提问作者amsjntz

