2014-09-30 63 views
0

我目前正在使用套接字進行多人遊戲,並且在登錄時遇到了一些問題。C++套接字,發送和recv不同步

這裏的服務器功能 - 線程從用戶收到的消息涉及:

void Server::ClientThread(SOCKET Connection) 
{ 
char *buffer = new char[256]; 

while (true) 
{ 
    ZeroMemory(buffer,256); 
    recv(Connection, buffer, 256, 0); 
    cout << buffer << endl; 
    if (strcmp(buffer, "StartLogIn")) 
    { 
     char* UserName = new char[256]; 
     ZeroMemory(UserName, 256); 
     recv(Connection, UserName, 256, 0); 

     char* Password = new char[256]; 
     ZeroMemory(Password, 256); 
     recv(Connection, Password, 256, 0); 

     cout << UserName << "-" << Password << " + "<< endl; 
     if (memcmp(UserName, "taigi100", sizeof(UserName))) 
     { 
      cout << "SMB Logged in"; 
     } 
     else 
      cout << "Wrong UserName"; 
    } 

    int error = send(Connection, "0", 1, 0); 
// error = WSAGetLastError(); 
    if (error == SOCKET_ERROR) 
    { 
     cout << "SMB D/Ced"; 
     ExitThread(0); 
    } 
} 
} 

,這裏是從客戶端發送數據到服務器的功能:

if (LogInButton->isPressed()) 
{ 
    send(Srv->getsConnect(), "StartLogIn", 256, 0); 
    const wchar_t* Usern = UserName->getText(); 
    const wchar_t* Passn = Password->getText(); 
    stringc aux = ""; 
    aux += Usern; 
    char* User = (char*)aux.c_str(); 

    stringc aux2 = ""; 
    aux2 += Passn; 
    char* Pass = (char*)aux2.c_str(); 

    if (strlen(User) > 0 && strlen(Pass) > 0) 
    { 
     send(Srv->getsConnect(), User, 256, 0); 
     send(Srv->getsConnect(), Pass, 256, 0); 
    } 
} 

我我會試圖儘可能簡單地解釋這一點。服務器端函數中while(true)的第一個recv函數首先接收「StartLogIn」,但不會輸入if,直到下一個循環結束。因爲它再次循環它變成「taigi100」(我使用的用戶名),然後它進入如果它甚至不應該。

解決此問題的一種方法是製作send-recv系統,以便在獲得一些反饋之前不發送任何其他內容。

我想知道是否有其他解決此問題的快速方法,以及爲什麼發生這種奇怪的行爲。

+0

在實施它之前,請大步退一步並記錄協議。這將爲您節省大量未來的痛苦。如果您不確定如何執行此操作,請查看TCP上分層的其他協議(例如SMTP,HTTP或IRC)的文檔。 – 2014-09-30 16:36:25

回答

3

那麼它充滿了錯誤。

  • 您過度使用新的[]。確定不是一個錯誤,但你不刪除任何這些,你可以使用任何本地堆棧緩衝區空間或vector<char>

  • 你需要經常檢查,recv任何調用的結果,你不能保證收到的號碼你期待的字節數。您指定的數字是緩衝區的大小,而不是您期望獲得的字節數。

  • STRCMP返回0,如果字符串匹配,非零值,如果他們不(實際上是1或-1取決於他們是否比較少或更大)。但看起來你使用非零來表示平等。

  • 不確定stringc是什麼。從寬字符串到字符串的某種轉換?在任何情況下,我認爲發送是常量正確的,所以不需要將常量拋棄。發送的

  • 第三個參數是要發送,而不是你的緩衝區的容量的字節數。用戶名和密碼可能不是256字節。你需要把它們發送一個「數據包」,但這樣的接收器知道他們做了什麼,並知道什麼時候他們已經收到一個完整的數據包。例如發送一個字符串,如「User = vandamon \ 0」。 (而你需要太檢查它的返回值)

+0

我還會添加「嘗試發送比可用數據更多的數據」,「不檢查'send'的返回值,這可能會部分成功,並且在使用流套接字時,」對'send'的單個調用可能無法映射給'recv'打個電話 – Hasturkun 2014-09-30 15:29:10

+0

上帝,我好傻,忘了!在strcmp ... – 2014-09-30 15:57:41

0

因爲send()recv()通話可能不匹配,兩個非常好的習慣進入是:(1)由一個固定大小的長度preceed所有可變長度的數據,和(2)只發送最低需求。

所以您最初的send()通話將被寫成如下:

char const * const StartLogin = "StartLogIn"; 
short const StartLoginLength = static_cast<short>(strlen(StartLogin)); 
send(Srv->getsConnect(), reinterpret_cast<char *>(&StartLoginLength), sizeof(short), 0); 
send(Srv->getsConnect(), StartLogin, StartLoginLength, 0); 

當時相應的接收碼將不得不讀取兩個字節,並保證它通過檢查recv()的返回值了他們,重試,如果沒有足夠收到。那麼它會循環第二次閱讀正是許多字節到緩衝區中。

int guaranteedRecv(SOCKET s, char *buffer, int expected) 
{ 
    int totalReceived = 0; 
    int received; 
    while (totalReceived < expected) 
    { 
     received = recv(s, &buffer[totalReceived], expected - totalReceived, 0); 
     if (received <= 0) 
     { 
      // Handle errors 
      return -1; 
     } 
     totalReceived += received; 
    } 
    return totalReceived; 
} 

請注意,這假設一個阻塞插座。如果沒有數據可用,非阻塞將返回零,並且errno/WSAGetLastError()會說* WOULDBLOCK。如果你想走這條路線,你將不得不專門處理這個案例,並找到一些方法來阻止數據可用。無論是忙或等待 - 等待數據,通過反覆調用recv()。啊。

無論如何,你首先打電話給這個短地址爲reinterpret_cast<char *>,預計== sizeof(short)。然後你有足夠的空間,然後再次調用以獲得有效載荷。請注意缺少尾部NUL字符,除非您明確地發送它們,而我的代碼不會。