服务器 (192.168.1.5:3001) 运行 Linux 3.2,设计为一次只接受一个连接。客户端 (192.168.1.18) 正在运行 Windows 7。连接是无线连接。这两个程序都是用 C++ 编写的。
它在 10 个连接/断开周期中有 9 个效果很好。根据 Wireshark(见截图)的说法,第十个(随机发生)连接让服务器接受连接,然后当它稍后实际写入它时(通常是 30 多秒后),看起来它正在写入一个旧的陈旧连接,带有客户端已 FIN(不久前)但服务器尚未 FIN 的端口号。所以客户端和服务器连接似乎不同步——客户端建立新连接,服务器尝试写入前一个连接。一旦进入这种断开状态,每个后续的连接尝试都会失败。可以通过超出最大无线范围半分钟来启动断开状态(如之前 10 次中有 9 次这样有效,但有时会导致断开状态)。
屏幕截图中的红色箭头表示服务器何时开始发送数据(Len != 0),即客户端拒绝它并向服务器发送 RST 的时间点。右边缘下方的彩色点表示使用的每个客户端端口号的单一颜色。请注意在该颜色的其余点之后出现的一两个点如何很好地出现(并注意时间列)。
问题看起来像是在服务器端,因为如果您终止服务器进程并重新启动,它会自行解决(直到下次发生)。
希望代码不会太不寻常。我将listen()中的队列大小参数设置为0,我认为这意味着它只允许一个当前连接而没有挂起的连接(我尝试了1,但问题仍然存在)。在代码中显示“//错误”的跟踪打印中,没有任何错误出现。
// Server code
mySocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (mySocket == -1)
{
// error
}
// Set non-blocking
const int saveFlags = ::fcntl(mySocket, F_GETFL, 0);
::fcntl(mySocket, F_SETFL, saveFlags | O_NONBLOCK);
// Bind to port
// Union to work around pointer aliasing issues.
union SocketAddress
{
sockaddr myBase;
sockaddr_in myIn4;
};
SocketAddress address;
::memset(reinterpret_cast<Tbyte*>(&address), 0, sizeof(address));
address.myIn4.sin_family = AF_INET;
address.myIn4.sin_port = htons(Port);
address.myIn4.sin_addr.s_addr = INADDR_ANY;
if (::bind(mySocket, &address.myBase, sizeof(address)) != 0)
{
// error
}
if (::listen(mySocket, 0) != 0)
{
// error
}
// main loop
{
...
// Wait for a connection.
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(mySocket, &readSet);
const int aResult = ::select(getdtablesize(), &readSet, NULL, NULL, NULL);
if (aResult != 1)
{
continue;
}
// A connection is definitely waiting.
const int fileDescriptor = ::accept(mySocket, NULL, NULL);
if (fileDescriptor == -1)
{
// error
}
// Set non-blocking
const int saveFlags = ::fcntl(fileDescriptor, F_GETFL, 0);
::fcntl(fileDescriptor, F_SETFL, saveFlags | O_NONBLOCK);
...
// Do other things for 30+ seconds.
...
const int bytesWritten = ::write(fileDescriptor, buffer, bufferSize);
if (bytesWritten < 0)
{
// THIS FAILS!! (but succeeds the first ~9 times)
}
// Finished with the connection.
::shutdown(fileDescriptor, SHUT_RDWR);
while (::close(fileDescriptor) == -1)
{
switch(errno)
{
case EINTR:
// Break from the switch statement. Continue in the loop.
break;
case EIO:
case EBADF:
default:
// error
return;
}
}
}
因此,在 accept() 调用(假设这正是发送 SYN 数据包的时间点)和 write() 调用之间的某个地方,客户端的端口被更改为先前使用的客户端端口。
所以问题是:服务器怎么会接受一个连接(并因此打开一个文件描述符),然后通过以前的(现在是陈旧的和死的)连接/文件描述符发送数据?它是否需要在缺少的系统调用中提供某种选项?