在Windows上超时使用select()时出现随机奇怪行为



我正在使用一个工具与游戏服务器通信。为了建立与游戏服务器的连接,我发送了一个登录包,然后从那里继续。我还使用了一个工具,它也做同样的事情,但它是由其他人用C#编写的,带有预先制作的库。这个应用程序在使用了几个小时后出现了一些stackoverflow异常问题,并且移植到linux也不太有趣,因此我决定用C++从头开始编写自己的应用程序。

我的脚本大致如下:

while (!connected) {
if (connectCounter == 0)
std::cout << "Trying to connect..." << std::flush;
else
std::cout << "." << std::flush; // add point
connectCounter++;
int selectSize = 0;
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
fd_set fds;
FD_ZERO(&fds);
FD_SET(mysocket, &fds);
selectSize = select(mysocket + 1, &fds, 0, 0, &timeout);
if (selectSize == 1) {
// we might now be logged in, check routines
connected = true;
}
} 

现在,我在两个应用程序中都随机出现了一个"bug",一个是别人用C#编写的,另一个是我自己的。我可能应该提到,我以前从未有过这种行为,但自从我格式化电脑后,我第一次看到这个问题出现了。

问题:游戏服务器离线了几个小时,电脑可能刚刚启动。游戏服务器仍然关闭,我启动了应用程序。现在它尝试登录,但由于游戏服务器仍处于脱机状态,因此没有成功。现在它写着"尝试连接"。由于超时设置,它应该等待5秒,然后在每次尝试失败后加1分。相反,它在不等待超时的情况下逐点触发。这种情况发生在其他人编写的C#应用程序和我自己的应用程序中。在这两个应用程序中,它只是随机发生的,并不是每次我启动应用程序时都发生。正如我所提到的,在格式化我的计算机之前,我从未遇到过这种问题。我还将这个应用程序移植到了我的linux服务器上,在linux上没有经历过这种行为。我的一个朋友也使用这两个应用程序,从未向我报告过这种问题

这对我来说太奇怪了,我不知道原因。从我得到的信息来看,这不可能真的与代码有关,因为它发生在两个完全不同的应用程序中,而且从我重新安装Windows后才能知道。

第1版:现在我发现了一些有趣的东西,我在windows和linux上添加了以下代码:

selectSize = select(mysocket + 1, &fds, 0, 0, &timeout);
std::cout << selectSize << std::cout;

有趣的是,在Windows上,我的控制台现在将输出:Trying to connect...0.1.0.1.0.1.0.1

重新启动应用程序并输出Trying to connect...0.0.0.0.0.1 在linux上,它总是返回Trying to connect...0.0.0.0.0,从来没有假阳性。

仍然只发生在窗户上。甚至不知道C#应用程序的人使用了什么方法,但在重新安装windows后,同样的问题也会随机发生。

编辑2:我想我发现问题了。

在超时设置和选择()之前,我正在用我的登录数据包发送()。我想无论出于什么原因,都会有一些返回,所以在某些情况下,selectSize可能会更改为1。这可能是在Windows上造成问题的原因吗,而它在linux上工作?

引用"POSIX规范(在线副本):

当对O_NONBLOCK清除的输入函数的调用不会阻塞时,无论函数是否成功传输数据,描述符都应被视为已准备好读取。(该函数可能返回数据,文件结束指示,或除指示被阻止之外的其他错误,在每种情况下,描述符都应被视为已准备好读取。)

所以我想说,为了修复您的代码,您必须另外检查"可供读取"的文件描述符是否没有任何错误或eof指示。

要检查套接字是否已连接,应该检查它的可写性,而不是可读性。更改

selectSize = select(mysocket + 1, &fds, 0, 0, &timeout);

selectSize = select(mysocket + 1, 0, &fds, 0, &timeout);

好吧,看来我终于找到了我最初的问题的至少部分答案,为什么linux在windows破坏我的应用程序时给我一个工作结果。根据我在windows平台上读到的内容,select()返回WSAECONNECTRESET,而不是阻塞或超时,请参阅:WinSock Recvfrom );现在返回WSAECONNRESET,而不是阻止或超时

因此,这似乎就是应用程序在linux上运行良好的原因(就我而言),其中select()似乎仍然返回超时,而Windows返回该错误并在一定程度上破坏了我的应用程序。

解决方案:所以我终于找到了解决方案。特别感谢那个提醒我使用Wireshark的人。起初,在离线时向游戏服务器发送登录数据包后,我认为select()在应该为0的情况下返回1是完全随机的,但事实上,我发现有时我会收到"ICMP端口无法访问",这导致select()返回1而不是0(见上面的链接)显然,我只想在服务器发出实际登录响应时,select()才返回1。在linux上,这是开箱即用的,不会造成任何问题。对于Windows,我发现了一个简单的修复方法,在select()函数之前添加了以下代码:

#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
DWORD lpcbBytesReturned = 0;
BOOL lpvInBuffer = FALSE;
WSAIoctl(mysocket, SIO_UDP_CONNRESET, &lpvInBuffer, sizeof(lpvInBuffer), NULL, 0, &lpcbBytesReturned, NULL, NULL);

最新更新