我们面临一个问题,从一段时间后,特定的套接字连接被阻塞,客户端的 tcp 内核不断重传 [ACK] 数据包。
拓扑流程如下:
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之后,而Server在RouterB之后。
给定两个用户,即 U1 和 U2。U1 从 PC1 登录,U2 从 PC3 登录。U1 和 U2 都会在自己和服务器之间建立一个 tcp 连接。现在 U1 能够通过其 tcp 连接向 Server 发送数据,然后 Server 将所有数据中继到 U2。直到这一刻一切正常。
表示U1和Server之间TCP连接的Server端点对应的socket号:U1-OldSocketFd
不要注销U1,拔掉PC1的线。然后 U1 从 PC2 登录,现在它与服务器建立了新的 TCP 连接。
表示U1和Server之间的TCP连接的Server端点对应的socket号:U1-NewSocketFd
在服务器端,当它使用 U1 更新其 Session 时,它会调用
close(U1-OldSocketFd)
.
4.1。在第 3 步之后大约 30 秒,我们发现 U1 无法通过其新的 TCP 连接向服务器发送任何数据。
4.2. 在步骤 3 中,如果 Server 没有close(U1-OldSocketFd)
立即调用(U1 和 Server 之间建立了相同的第二个新连接),而是 Server
close(U1-OldSocketFd)
在超过 70-80 秒后调用,则一切正常。
UP2。路由器 B 在端口 8013 上使用端口转发
。UP3。运行服务器的 Linux 操作系统的一些参数。
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1