[英]Randomly strange behaviour when using select() with timeout on Windows
我正在使用一種工具與游戲服務器通信。 要建立與游戲服務器的連接,我要發送一個登錄數據包,然后從那里繼續。 我還使用了一個功能相同的工具,但該工具是由其他人用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;
}
}
現在,在這兩個應用程序中,一個隨機出現在我身上的“錯誤”,一個是由其他人用C#編寫的,另一個是由我自己編寫的。 我可能應該提到我以前從未有過這種行為,但是自從格式化計算機以來,我第一次看到這個問題。
問題:Gameserver離線了幾個小時,計算機可能是全新啟動的。 Gameserver仍然關閉,我啟動了該應用程序。 現在,它嘗試登錄,但由於游戲服務器仍處於離線狀態而無法成功。 現在,它寫“嘗試連接”。 由於超時設置,它應該等待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
,永遠不會出現誤報。
仍然只在Windows上發生。 甚至不知道C#應用程序中的家伙使用的方法是什么,但是在重新安裝Windows之后,隨機發生相同的問題。
編輯2:我想我找到了問題。
在設置超時和select()之前,我正在使用我的登錄數據包執行sendto()。 我想無論出於什么原因都會有返回的輸入,因此在某些情況下selectSize可能會更改為1。 在Linux上運行時,這是否有可能在Windows上引起問題?
引用“ the” POSIX規范(在線副本) :
當對O_NONBLOCK清除的輸入函數的調用不會阻塞時,無論該函數是否成功傳輸數據,都應認為描述符已准備就緒,可以讀取。 (該函數可能返回數據, 文件結束指示或錯誤,而不是一個錯誤,表明該數據已被阻止,並且在每種情況下,都應將描述符視為已准備好進行讀取。 )
所以我想,以解決您的代碼說你必須另外檢查是否文件描述符“讀就緒”沒有任何錯誤或EOF指示。
要檢查套接字是否已連接,應檢查其可寫性,而不是可讀性。 更改
selectSize = select(mysocket + 1, &fds, 0, 0, &timeout);
至
selectSize = select(mysocket + 1, 0, &fds, 0, &timeout);
好的,看來我終於找到了對最初問題的至少部分答案,為什么在Windows中斷我的應用程序時linux給我一個有效的結果。 從我在Windows平台上閱讀的內容中,select()返回WSAECONNECTRESET而不是阻止或超時,請參見: WinSock Recvfrom()現在返回WSAECONNRESET而不是阻止或超時
因此,這似乎是應用程序在Linux上運行良好(出於我的目的)的原因,其中select()似乎仍返回超時,而Windows返回該錯誤並在一定程度上中斷了我的應用程序。
解決方案:所以我終於找到了解決方法。 特別感謝提醒我使用Wireshark的那個人。 起初,我在將登錄數據包發送到gameserver時(離線時)將其應該為0的select()賦值為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);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.