2014-10-07 83 views
0

我有一臺服務器使用雙線程系統來管理100到200個併發連接。它使用TCP套接字,因爲數據包傳輸保證很重要(這是一個通信系統,在這種系統中,未能通過遠程API調用FUBAR客戶端)。Windws C++間歇性套接字斷開連接

我已經實現了一個自定義協議層,將傳入的字節分成數據包並正確分發它們(庫包含在下面)。我意識到使用MSG_PEEK的問題,但就我所知,它是唯一能夠滿足庫實現需求的系統。我願意接受建議,特別是如果這可能是問題的一部分。

基本上,問題是,儘管客戶端每隔4次成功發送一次keepalive數據包,但由於缺少傳入數據包的時間超過20秒,服務器將隨機丟棄客戶端的套接字。我可以驗證服務器本身沒有脫機,並且遇到問題的用戶(包括我自己)的連接穩定。

用於發送該庫/接收是在這裏:

short ncsocket::send(wstring command, wstring data) { 
wstringstream ss; 
int datalen = ((int)command.length() * 2) + ((int)data.length() * 2) + 12; 
ss << zero_pad_int(datalen) << L"|" << command << L"|" << data;   
int tosend = datalen; 
short __rc = 0; 
do{ 
    int res = ::send(this->sock, (const char*)ss.str().c_str(), datalen, NULL); 
    if (res != SOCKET_ERROR) 
     tosend -= res; 
    else 
     return FALSE; 
    __rc++; 
    Sleep(10); 
} while (tosend != 0 && __rc < 10); 
if (tosend == 0) 
    return TRUE; 
return FALSE; 
} 

short ncsocket::recv(netcommand& nc) { 
vector<wchar_t> buffer(BUFFER_SIZE); 
int recvd = ::recv(this->sock, (char*)buffer.data(), BUFFER_SIZE, MSG_PEEK); 
if (recvd > 0) { 
    if (recvd > 8) { 
     wchar_t* lenstr = new wchar_t[4]; 
     memcpy(lenstr, buffer.data(), 8); 
     int fulllen = _wtoi(lenstr); 
     delete lenstr; 

     if (fulllen > 0) { 
      if (recvd >= fulllen) { 
       buffer.resize(fulllen/2); 
       recvd = ::recv(this->sock, (char*)buffer.data(), fulllen, NULL); 
       if (recvd >= fulllen) { 
        buffer.resize(buffer.size() + 2); 
        buffer.push_back((char)L'\0'); 
        vector<wstring> data = parsewstring(L"|", buffer.data(), 2); 
        if (data.size() == 3) { 
         nc.command = data[1]; 
         nc.payload = data[2]; 
         return TRUE; 
        } 
        else 
         return FALSE; 
       } 
       else 
        return FALSE; 
      } 
      else 
       return FALSE; 
     } 
     else { 
      ::recv(this->sock, (char*)buffer.data(), BUFFER_SIZE, NULL); 
      return FALSE; 
     } 
    } 
    else 
     return FALSE; 
} 
else 
    return FALSE; 

}

這是用於確定是否太多時間已經過去的代碼:

if ((int)difftime(time(0), regusrs[i].last_recvd) > SERVER_TIMEOUT) { 
       regusrs[i].sock.end(); 
       regusrs[i].is_valid = FALSE; 
       send_to_all(L"removeuser", regusrs[i].server_user_id); 

       wstringstream log_entry; 
       log_entry << regusrs[i].firstname << L" " << regusrs[i].lastname << L" (suid:" << regusrs[i].server_user_id << L",p:" << regusrs[i].parent << L",pid:" << regusrs[i].parentid << L") was disconnected due to idle"; 
       write_to_log_file(server_log, log_entry.str()); 
      } 

的「regusrs [I ]「正在使用我用來描述套接字描述符和用戶信息的向量的當前迭代成員。 'is_valid'檢查可以告訴關聯用戶是否是實際用戶 - 這樣做是爲了防止系統不得不釋放向量的成員 - 它只是將其返回到可用槽的池中。沒有線程訪問/超出範圍的問題。

無論如何,我開始懷疑是否服務器本身就是問題所在。我目前正在另一臺服務器上進行測試,但我想知道是否有另一組眼睛可以阻止某些不合適的位置,或者提示我插入一個概念,並使用我不知道的擴展Keepalive。

在此先感謝!

+0

首先做循環是越野車;如果初始發送沒有接收到所有數據,則從頭開始再次發送,而不僅僅是第一次發送的數據。不確定這是否解釋了問題。你如何發送Keepalive數據包?你是否檢查過電線上的數據以確保它們實際上是及時傳輸的? – 2014-10-07 02:02:24

+0

我還沒有檢查線路上的線路,因爲我無法可靠地重新創建線路。我已連接到服務器(因爲我開始在不同的機器上託管它)連續幾個小時。我認爲服務器更改和切換MSG_PEEK的組合將有所幫助。 – 2014-10-07 04:21:06

回答

2

我想我看到你在做什麼與MSG_PEEK,你等待,直到它看起來像你有足夠的數據來讀取一個完整的數據包。但是,我會懷疑這一點。 (很難僅通過查看源的這一小部分,而不是整個事情,以確定您的系統的動態行爲。)

要避免使用MSG_PEEK,請遵循以下兩個原則:

  1. 當你得到一個數據準備好的通知(我假設你使用的是select),然後讀取全部來自recv()的等待數據。您可以使用多個recv()呼叫,因此您可以分段處理傳入數據。

  2. 如果您只讀取了部分數據包(長度或有效負載),然後將其保存在某處以便下次獲得讀取通知。將數據包和有效負載重新組合在一起,不要將它們留在套接字緩衝區中。

順便說一句,使用new/memcpy/wtoi/delete悲慘是低效的。你根本不需要分配內存,你可以使用局部變量。然後你根本不需要memcpy,只是一個演員。

我假定你已經假設你的數據包的長度不能超過999個字節。

+0

我將切換到select()和一個內部緩衝區,但是,效率與否,我們使用字符串(UTF-16)字符串長度來與移動設備兼容。我不想和endian-ness一起爭取像長度參數那樣簡單的事情。 你認爲MSG_PEEK的不可靠性是爲什麼我有隨機斷線? – 2014-10-07 04:17:46

+0

我對'new'和'memcpy'的評論與endian-ness無關。我不知道'MSG_PEEK'是否被認爲是不可靠的,只是我從來沒有用過它,所以我不熟悉它的特點。 – 2014-10-07 06:20:59