我们面临一个问题,从一段时间开始,特定的套接字连接被阻塞,客户端的TCP内核不断重传[ACK]数据包。
拓扑流如下:
这里是通过WireShark捕获的数据包:
A)服务器
拓扑流如下:
Client A ←→ Switch A ← Router A:NAT ← .. Internet ..
→ Router B:NAT → Switch B ←→ Server B
这里是通过WireShark捕获的数据包:
A)服务器
1. 8013 > 6757 [PSH, ACK] Seq=56 Ack=132 Win=5840 Len=55
2. 6757 > 8013 [ACK] Seq=132 Ack=111 Win=65425 Len=0
B) 客户端
//lines 3 and 4 are exactly the same as line 1 and 2
3. 8013 > 13000 [PSH, ACK] Seq=56 Ack=132 Win=5840 Len=55
4. 13000 > 8013 [ACK] Seq=132 Ack=111 Win=65425 Len=0
5. 13000 > 8013 [PSH, ACK] Seq=132 Ack=111 Win=65425 Len=17
[TCP Retransmission]
6. 13000 > 8013 [PSH, ACK] Seq=132 Ack=111 Win=65425 Len=17
8013是服务器端口,6757是客户端NAT端口。
TCP内核为什么会不停地发送[ACK]数据包来告诉客户端已经收到第1个数据包(见数据包4、5和6),即使服务器已经接收到一个[ACK]数据包(见数据包2)?该连接的任一方在出现问题时都不关闭套接字。
在数据包6之后,连接丢失,我们不能再通过该套接字向服务器发送任何数据。
psuedocode:
//client
serverAddr.port =htons(8013) ;
serverAddr.ip = inet_addr(publicIPB);
connect(fdA, serverAddr,...);
//server
listenfd = socket(,SO_STREAM,);
localAddr.port = htons(8013);
localAddr.ip = inet_addr(INADDR_ANY);
bind(localAddr...)
listen(listenfd, 100);
...
//using select model
select(fdSet, NULL, NULL, NULL);
for(...)
{
if (FD_ISSET(listenfd))
{
...
}
...
}
更新
UP1. 这里是重现问题的具体步骤。
有三台电脑,分别是PC1、PC2和PC3。所有三台电脑都在RouterA后面,而服务器在RouterB后面。
有两个用户,分别是U1和U2。U1从PC1登录,U2从PC3登录。U1和U2都会建立与服务器之间的TCP连接。现在U1能够通过它的TCP连接向服务器发送数据,然后服务器将所有数据中继给U2。一切正常到这一刻。
表示与U1和服务器之间的TCP连接相对应的套接字号码: U1-OldSocketFd
不要注销U1,并拔掉PC1的电缆。然后U1从PC2登录,现在它与服务器建立了一个新的TCP连接。
表示与U1和服务器之间的TCP连接相对应的套接字号码: U1-NewSocketFd
从服务器端来看,当它更新其与U1的会话时,它调用
close(U1-OldSocketFd)
。
4.1. 在第3步大约30秒后,我们发现U1无法通过它的新TCP连接向服务器发送任何数据。
4.2. 在第3步中,如果服务器不立即调用close(U1-OldSocketFd)
(在U1和服务器之间的新连接建立的同一秒钟),而是在70-80秒后调用close(U1-OldSocketFd)
,那么一切都正常。
UP2. Router B在端口8013上使用端口转发。
UP3. 服务器运行的Linux操作系统的一些参数。
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1