2011-09-21 88 views
3

我正在爲遊戲製作一個非常簡單的大廳系統。每個客戶端定期通過UDP廣播兩個數據包,以最初發現其他客戶端並傳輸用戶信息,準備就緒等。該遊戲正在針對Windows和Linux(64位)進行開發。正在發送UDP廣播,但未收到

在Windows端,我已經得到了大廳系統完美無缺的工作。當我在一臺Windows機器上進入大廳時,該人在其他機器中彈出。同樣,立即檢查和斷開連接。換句話說,它是有效的。

現在的問題:Linux。網絡代碼幾乎完全相同,只有少數必要的平臺特定更改。我第一次嘗試Windows < - > Linux。使用Wireshark,我發現Linux方面的確在廣播數據包並從Windows中接收它們,但是遊戲從未抓住數據包。我在select語句(套接字而不是套接字+ 1)中發現了一個錯誤,但修復它並沒有幫助。 Windows盒子廣播數據包,但它根本沒有收到來自Linux盒子的數據包!然後我發現雖然兩臺機器都在廣播和接收(再次通過Wireshark確認),但兩臺機器上的遊戲都無法「看見」數據包。我很確定它不是一個防火牆問題(在任一平臺上關掉所有東西,測試,恢復一切,沒有變化),網絡連接似乎沒問題(可以手動ping每臺主機)。我也檢查過以確保端口確實可用(它們是)。

下面是廣播包代碼:

void NetworkLinux::BroadcastMessage(const std::string &msg, 
      const char prefix) 
    { 
     string data(prefix + msg); 

     if (sendto(linuxSocket, data.c_str(), static_cast<int>(data.length()), 0, 
     reinterpret_cast<sockaddr*>(&broadcastAddr), sizeof(broadcastAddr)) == -1) 
     { 
      Display_PError("sendto"); 
     } 
    } 

和接收數據包的代碼:

const Message NetworkLinux::ReceiveMessage() 
    { 
     char buffer[recvBufferLength]; 
     fill(buffer, buffer + recvBufferLength, 0); 
     sockaddr_in sender; 
     int senderLen = sizeof(sender); 

     fd_set read_fds; 
     FD_ZERO(&read_fds); 
     FD_SET(linuxSocket, &read_fds); 

     timeval time; 
     time.tv_sec = 0; 
     time.tv_usec = 16667; // microseconds, so this is ~1/60 sec 

     int selectResult = select(linuxSocket + 1, &read_fds, 
             nullptr, nullptr, &time); 
     if (selectResult == -1) 
     { 
      Display_PError("select"); 
     } 
     else if (selectResult > 0) // 0 means it timed-out 
     { 
      int receivedBytes = recvfrom(linuxSocket, buffer, 
         recvBufferLength, 0, reinterpret_cast<sockaddr*>(&sender), 
         reinterpret_cast<socklen_t*>(&senderLen)); 

      if (receivedBytes == -1) 
      { 
       Display_PError("recvfrom"); 
      } 
      else if (receivedBytes > 0) 
      { 
       Message msg; 
       msg.prefix = buffer[0]; 
       msg.msg = string(buffer + 1, buffer + receivedBytes); 
       msg.address = sender.sin_addr; 
       return msg; 
      } 
     } 
     Message m; 
     m.prefix = 'N'; 
     return m; 
    } 

爲什麼select()繼續回來爲0的時候,我可以看到的數據包到達?此外,爲什麼它在Windows < - > Windows場景下工作,但不是Linux < - > Linux或Linux < - > Windows?

編輯:這是套接字創建/設置代碼,根據要求。計算出的示例IP /廣播地址爲:192.168.1.3/192.168.1.255,192.168.1.5/192.168.1.255,它們與Windows端生成和使用的內容相匹配。

bool NetworkLinux::StartUp() 
    { 
     // zero addr structures 
     memset(&machineAddr, 0, sizeof machineAddr); 
     memset(&broadcastAddr, 0, sizeof broadcastAddr); 

     // get this machine's IP and store it 
     machineAddr.sin_family = AF_INET; 
     machineAddr.sin_port = htons(portNumber); 
     inet_pton(AF_INET, GetIP().c_str(), &(machineAddr.sin_addr)); 

     // get the netmask and calculate/store the correct broadcast address 
     broadcastAddr.sin_family = AF_INET; 
     broadcastAddr.sin_port = htons(portNumber); 
     GetNetMask(); 
     broadcastAddr.sin_addr.s_addr = machineAddr.sin_addr.s_addr | ~netmask; 

     char bufIP[INET_ADDRSTRLEN], bufBroadcast[INET_ADDRSTRLEN]; 
     inet_ntop(AF_INET, &machineAddr.sin_addr, bufIP, INET_ADDRSTRLEN); 
     inet_ntop(AF_INET, &broadcastAddr.sin_addr, bufBroadcast, 
       INET_ADDRSTRLEN); 
     Log("IP is: " + string(bufIP) + "\nBroadcast address is: " 
       + string(bufBroadcast)); 

     // create socket 
     linuxSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 
     if (linuxSocket == -1) 
     { 
      Display_PError("socket"); 
      return false; 
     } 
     Log("Socket created."); 

     // switch to broadcast mode 
     int broadcast = 1; 
     if (setsockopt(linuxSocket, SOL_SOCKET, SO_BROADCAST, &broadcast, 
       sizeof broadcast) == -1) 
     { 
      Display_PError("setsockopt"); 
      close(linuxSocket); 
      return false; 
     } 
     Log("Socket switched to broadcast mode."); 

     // bind it (this simplifies things by making sure everyone is using the same port) 
     if (bind(linuxSocket, reinterpret_cast<sockaddr*>(&machineAddr), 
       sizeof(machineAddr)) == -1) 
     { 
      Display_PError("bind"); 
      close(linuxSocket); 
      return false; 
     } 
     Log("Socket bound."); 

     return true; 
    } 
+3

相關的東西:你應該使用多播代替廣播,這是對網絡更好的(後來你也可以通過多個網絡途徑的東西,或者可以開始使用IPv6更容易,它不支持廣播)。另外,你有沒有考慮過使用一些庫來處理大部分的東西?這應該會減少可能的錯誤點數量。 – PlasmaHH

+0

您還需要展示如何設置套接字。 'sockaddr_in','setsockopt()'等等。 –

+0

@PlasmaHH我曾經看過多播,但讀過一些路由器不支持它;但是,我仍然可以在未來看到它。無論如何,這只是因爲它*應該*工作而困擾我,但並不是。我查看的大多數庫都需要某種服務器地址,這在這種情況下不起作用,因爲客戶端不知道局域網中有哪些用戶。 – Gemini14

回答

4
machineAddr.sin_port = htons(portNumber); 
inet_pton(AF_INET, GetIP().c_str(), &(machineAddr.sin_addr)); 

bind(linuxSocket, reinterpret_cast<sockaddr*>(&machineAddr), 

此套接字綁定到只由GetIP返回本機地址,這可能不是你想要的送到portNumber接受的數據包,你也想接收發送的數據包到廣播地址的端口。您可能希望將sin_addr設置爲INADDR_ANY,這是通配符地址,它將允許套接字以某種方式接收發送到端口的任何地址的數據包。

+1

優秀...這(幾乎)完全是解決方案!將Linux代碼更改爲使用INADDR_ANY使兩個Linux客戶端都可以看到對方。奇怪的是,我發現改變Windows使用INADDR_ANY *沒有工作。然而,離開它綁定到機器的實際地址* *沒有工作,在所有配置(Linux的<->取勝,<->贏)。謝謝! – Gemini14

0

猜你忘了設置SO_BROADCAST套接字選項和廣播數據包將被過濾掉。

+0

設置該選項實際上是我所做的第一件事情之一。 – Gemini14