2013-12-20 61 views
0

我寫了一個簡單的客戶端服務器程序。 Network.h是一個頭文件,它使用Winsock2.h(TCP/IP模式)創建套接字,在阻塞模式下接受/連接,在非阻塞模式下發送/接收。我做了這樣的功能string TNetwork::Recv(int size)將返回字符串「無」,如果它得到WSAWOULDBLOCK錯誤(沒有數據尚未收到)Winsock2 tcp/ip - 一些數據包被忽略可能是由於前一個數據包的空終止符

這裏是我的主要功能:

int main(){ 
    string Ans; 
    TNetwork::StartUp(); //WSA start up, etc 
    cin >> Ans; 
    if (Ans == "0"){ // 0 --> server 
     TNetwork::SetupAsServer(); //accept connection (in blocking mode!) 
     while (true){ 
      TNetwork::Send("\nAss" + '\0'); //without null terminator, the client may read extra bytes, causing undefined behavior (?) 
      TNetwork::Send("embly" + '\0'); 
      cin >> Ans; 
     } 
    } 
    else{ // others --> regard Ans as IP address. e.g. I can type "127.0.0.1" 
     TNetwork::SetupAsClient(Ans); 
     string Rec; 
     while (true){ 
      Rec = TNetwork::Recv(1000); 
      if (Rec != "Nothing"){ 
       cout << Rec; 
      } 
     } 
    } 
    system("PAUSE"); 
} 

據稱,客戶端將打印「彙編」連接時,以及服務器何時將任何內容輸入到其控制檯窗口。有時候,客戶端只會在控制檯中打印出「\ nAss」,而不會顯示「embly。

據我的理解,TCP/IP確保所有數據都以正確的順序發送,所以我猜會發生什麼是兩個數據包同時到達,這通常發生在不穩定的互聯網上,而且由於這個終止符爲null,客戶端會忽略「embly」,因爲Recv()函數在遇到空終止符時停止讀取。

所以,我怎麼能保證客戶端將始終正確地讀取所有的數據包?

+0

如果有解決方案發送沒有空終止符的數據包,不知何故,我也不介意。如果它有效,我會更改代碼的任何部分。我對winsock2比較陌生(以及套接字編程),所以我可能有一些基本的錯誤。 –

+0

空終止和TCP沒有任何關係。你在這裏吠叫錯誤的樹。 TCP發送和接收API採用指針和長度參數,並且它們不對任何內容進行解釋。 – EJP

回答

0

是,網絡堆棧會以正確的順序發送數據,並且不關心你用什麼終端類型。這與你如何接收和處理數據(注意:不是數據包,)有關。如果您收到全部11個字節並將其打印到屏幕上,則該功能將在達到零時停止,但其餘數據仍然存在。

注意:因爲它是一個流,所以如果從流中只接收到10個字節的數據會發生什麼?您需要掃描零接收的內容,以瞭解您是否收到完整的「零終止字符串」,如果這是您想要傳送數據的方式。

編輯:此外,我不認爲"\nAss" + '\0'是做你認爲它是。而不是在字符串的末尾添加0字符(順便說一下,它已經有一個字符),它將0添加到您的字符串指針。

+0

謝謝你指出我的錯誤!早些時候,我試圖打印出我傳遞給recv的* char變量,而不發送空終止符,我會收到隨機字節,但我沒有意識到它來自print函數,所以我認爲winsock需要一個空終止符XP。另外我應該知道所有字符串對象都已經有空終止符,但是我錯過了這個事實。一切都很清楚:D。 –

0

正如@mark指出的那樣,TCP是關於流的,而不是數據包。 TCP負責確保數據可靠地從A傳輸到B,並且數據按照傳輸的順序傳遞給消費者。是的,數據被打包在線路上,但系統上的TCP堆棧接收這些數據包,並通過recv()函數構建可供您使用的數據流。 TCP堆棧處理無序數據,丟失數據和重複數據,以便在應用程序看到它時,該流是發送者發送時的鏡像副本。

要正確接收TCP數據,通常需要某種從套接字讀取數據的循環。我通常這樣做的方式是有一個專用於服務套接字的線程。線程函數是一個循環,當套接字變得可用時從套接字讀取數據,否則空閒。該循環將數據讀入例如1 KB的緩衝區。一旦將數據從套接字接收到此緩衝區中,緩衝區將被複制到另一個線程進行處理。處理線程的線程函數是一個從套接字線程接收1 KB緩衝區的循環,並將它們添加到例如1 MB的主緩衝區的後端。處理線程然後將消息處理出該主緩衝區並使其可供應用程序使用。

對於一個簡單的演示應用程序,兩個線程可能會矯枉過正。我描述的兩個線程當然可以合併爲一個線程,但對於我的應用程序來說,擁有兩個線程並利用我係統上的多個內核會更高效。關鍵是,如果你將要有一個前端UI,那麼至少有一個線程並沒有辦法解決,而且UI的響應速度還是很快。

另一件事。協議設計有兩種常用的機制。您正在使用一個標記(例如,空終止符等)來標記消息的開始/結束。我不喜歡這種機制,主要是因爲標記可能實際上需要在某些時候成爲消息的一部分。另一種機制是在每條消息上都有一個標題,至少告訴消息的長度。我更喜歡這種機制,並在我的頭文件中包含同步字和消息類型。例如,

struct Header 
{ 
    __int16 _sync; // a hex pattern, e.g., 0xABCD 
    __int16 _type; 
    __int32 _length; 
} 

這是一個總的8個字節。所以當從主緩衝區處理時,我讀取前8個字節,驗證同步字,並獲得長度。我確定主緩衝區中是否有'長度'字節可用。如果沒有,我必須等到套接字線程再次檢查之前爲我提供更多數據。如果是這樣,我從主緩衝區中提取「長度」字節,並將其傳遞給根據指定類型創建的對象,該對象知道如何解釋該特定消息。然後重複。

正如我所提到的,我使用了1 MB左右的主緩衝區。在處理消息時,將它們從主緩衝區中刪除非常重要,因此在後端有足夠的空間可用於新的數據。這涉及簡單地將未處理的數據(如果有的話)複製到緩衝區的開始處。在數據處理速度快於您可以處理數據的情況下,主緩衝區可能需要調整自身大小以容納額外數據。

我希望這不是壓倒性的。開始簡單並隨時添加。

+0

謝謝,這也是一個非常好的答案!我的方法幾乎與您所描述的方法相同,除了我使用字符串作爲主緩衝區,這會在接收到空終止符時導致問題。 感謝您指出在郵件中使用標題。我應該考慮在未來的應用中使用它。 –