2012-07-19 115 views
1

我正在製作一個可能需要處理3000多個連接的遊戲服務器(TCP)。 目前它有50個連接,我已經遇到滯後。Winsock發送呼叫很慢

我發現它的winsock send()調用每次需要100〜300ms才能返回,因爲它是單線程服務器,所以整個服務器的速度非常慢。

到目前爲止,我已經想到了兩種解決方案。

  1. 重新設計我的服務器,爲每個客戶端創建一個線程(爲3000個客戶端創建3000多個線程是否真的很穩定?)。
  2. 找到一種方法使send()調用立即返回。

這是我的套接字初始化代碼:

int ret = WSAStartup(MAKEWORD(2,2), &wsaData); 
if(ret != 0) 
{ 
    printf("Winsock failed to start.\n"); 
    system("pause"); 
    return; 
} 

server.sin_family = AF_INET; 
server.sin_addr.s_addr = INADDR_ANY; 
server.sin_port = htons(52000); 

sock = socket(AF_INET, SOCK_STREAM, 0); 
if(sock == INVALID_SOCKET) 
{ 
    printf("Invalid Socket.\n"); 
    system("pause"); 
    return; 
} 

if(bind(sock, (sockaddr*)&server, sizeof(server)) != 0) 
{ 
    printf("error"); 
    return; 
} 

if(listen(sock, 5) != 0) 
{ 
    printf("error"); 
    return; 
} 

在一個單獨的線程接受代碼

sockaddr_in from; 
int fromlen = sizeof(from); 
SOCKET sTmpSocket = accept(ServerSock, (struct sockaddr*)&from, &fromlen); 

我的發送功能

void CClient::SendPacket(BYTE* pPacket, DWORD Len) 
{ 
    DWORD ToSendLen = Len; 
    while (ToSendLen) 
    { 
     int iResult = send(this->ClientSocket, (char*)(pPacket + Len - ToSendLen), ToSendLen, 0); 

     if (iResult == SOCKET_ERROR) 
      return; 

     ToSendLen -= iResult; 
    } 
} 

這是我的服務器線程(不完整,只有相關部分)

while (1) 
{ 
    for (int i = 0; i < MAX_CLIENT; i++) 
    { 
     if (Clients[i].bConnected == false) 
      continue; 

     timeval timeout; 
     timeout.tv_sec = 0; 
     timeout.tv_usec = 1; 
     fd_set socketset; 
     socketset.fd_count = 1; 
     socketset.fd_array[0] = Clients[i].ClientSocket; 
     if (select(0, &socketset, 0, 0, &timeout)) 
     { 
      int RecvLen = recv(Clients[i].ClientSocket, (char*)pBuffer, 10000, MSG_PEEK); 
      if (RecvLen == SOCKET_ERROR) 
      { 
       Clients[i].bConnected = false; 
       iNumClients--; 
      } 
      else if (RecvLen == 0) 
      { 
       Clients[i].bConnected = false; 
       iNumClients--; 
      } 
      else if (RecvLen > 0) 
      { 
       // Packet handling here 
       recv(Clients[i].ClientSocket, (char*)pBuffer, dwDataLen, MSG_WAITALL); 

       //... 
      } 
     } 
    } 

    Sleep(1); 
} 

任何幫助將不勝感激。 謝謝。

+0

這就是爲什麼遊戲使用UDP - 雖然UDP不能保證通過數據包接收。另外,您發送的數據有多大? – BugFinder 2012-07-19 13:15:12

+0

您應該立即找到使發送呼叫立即返回的方法。對於服務器來說,3000個這樣的服務器將非常昂貴。我不知道關於winsock的具體內容,所以不會回答這個問題,但在C#中可以通過使用異步調用來完成。 – AlexDev 2012-07-19 13:16:01

+0

遊戲服務器正在使用TCP作爲房間列表和重要數據包......這是我遇到瓶頸的地方。 我使用UDP作爲玩家動作的遊戲玩法等。 – user1537941 2012-07-19 13:57:05

回答

4

我會去IOCPIO completion ports),它可以帶來理想的表現,並且可以很好地擴展。看看Boost.Asio,它使用IOCP(在窗口中)。

擁有3000個以上線程的想法非常糟糕,實際上並沒有擴展(就內存消耗和上下文切換而言)!

+0

我看着IOCP,所有我發現它是幫助我檢測套接字事件而不是做for循環。 如何幫助send()通話延遲? – user1537941 2012-07-19 14:04:01

+1

IOCP有助於使發送操作異步。請參閱[boost :: asio :: async_write(..)](http://www.boost.org/doc/libs/1_50_0/doc/html/boost_asio/reference/async_write.html) – Simon 2012-07-19 17:12:17

0

立即想到一件事,那就是使用非阻塞套接字並檢查套接字是否可用select寫入。

作爲一個側面說明,您使用的錯誤來自select的返回值。它在超時時返回0,如果在任何集合中都有套接字,則返回一個正數,如果有錯誤則返回-1。你不檢查錯誤。此外,雖然在winsock版本中不需要,但select的第一個參數實際上應該是最高套接字號加1。

還有一點,你只使用select一個客戶端的時間,加上所有客戶端的設置,然後做一個select

fd_set readset; 
FD_ZERO(&readset); 
SOCKET max_socket = 0; 

for (int i = 0; i < MAX_CLIENT; i++) 
{ 
    if (Clients[i].bConnected) 
    { 
     FD_SET(Clients[i].ClientSocket, &readset); 
     max_socket = std::max(max_socket, Clients[i].ClientSocket); 
    } 
} 

int res = select(max_socket + 1, &readset, 0, 0, &timeout); 
if (res == SOCKET_ERROR) 
    std::cout << "Error #" << WSAGetLastError() << '\n'; 
else if (res > 0) 
{ 
    for (int i = 0; i < MAX_CLIENT; i++) 
    { 
     if (Clients[i].bConnected && FD_ISSET(Clients[i].ClientSocket, &readset)) 
     { 
      // Read from client 
     } 
    } 
} 
+0

您的想法是爲每個客戶端創建一個發送緩衝區,追加每次在該緩衝區發送的數據,然後在一個單獨的線程中檢查套接字是否可寫,然後爲每個客戶端調用winsock send()。 如果套接字是可寫的,winsock send()調用將不會花費100ms以上的時間? – user1537941 2012-07-19 14:15:05

+0

哦,這個想法是錯誤的。 fdset最多可以處理64個套接字。 – user1537941 2012-07-19 16:11:30

+0

@ user1537941默認情況下,'fd_set'應該能夠包含1024個套接字。在包含''之前可能會增加'#define FD_SETSIZE'。 – 2012-07-20 05:34:53

0

另一種方法要考慮的是有多個工作線程處理一部分活動連接並使其可配置。例如,您可以爲每個內核創建n個線程,然後使用n進行試驗。

這應該會給你很多多線程的好處,而不會爲每個客戶端的一個線程的極端。

+0

是的,它會提供一個提升,但由於300ms +每次發送呼叫仍會阻止。 – user1537941 2012-07-19 15:10:17

0

如果您只發送小包,請使用setsockopt()設置TCP_NODELAY選項以開始。