2011-11-17 56 views
0

我正在實施基於Windows的Web服務器,該服務器使用WinSock2來處理來自客戶端的多個特定HTTP請求。我有一個課程來啓動和停止我的服務器。它看起來是這樣的:WinSock2:使用recv處理已接收的傳入連接併發送

class CMyServer 
{ 
    // Not related to this question methods and variables here 
    // ... 

public: 

    SOCKET m_serverSocket; 

    TLM_ERROR Start(); 
    TLM_ERROR Stop(); 
    static DWORD WINAPI ProcessRequest(LPVOID pInstance); 
    static DWORD WINAPI Run(LPVOID pInstance); 
} 

其中TLM_ERROR是我的服務器的錯誤枚舉類型定義。

bool CMyServer::Start()方法啓動服務器創建一個套接字監聽配置的端口上,並創建一個單獨的線程DWORD CMyServer::Run(LPVOID)接受像這裏所描述的傳入連接:

// Creating a socket 
    m_serverSocket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    if (m_serverSocket == INVALID_SOCKET) 
    return TLM_ERROR_CANNOT_CREATE_SOCKET; 

    // Socket address 
    sockaddr_in serverSocketAddr; 
    serverSocketAddr.sin_family = AF_INET;         // address format is host and port number 
    serverSocketAddr.sin_addr.S_un.S_addr = inet_addr(m_strHost.c_str()); // specifying host 
    serverSocketAddr.sin_port = htons(m_nPort);        // specifying port number 

    // Binding the socket 
    if (::bind(m_serverSocket, (SOCKADDR*)&serverSocketAddr, sizeof(serverSocketAddr)) == SOCKET_ERROR) 
    { 
    // Error during binding the socket 
    ::closesocket(m_serverSocket); 
    m_serverSocket = NULL; 
    return TLM_ERROR_CANNOT_BIND_SOCKET; 
    } 

    // Starting to listen to requests 
    int nBacklog = 20; 
    if (::listen(m_serverSocket, nBacklog) == SOCKET_ERROR) 
    { 
    // Error listening on socket 
    ::closesocket(m_serverSocket); 
    m_serverSocket = NULL; 
    return TLM_ERROR_CANNOT_LISTEN; 
    } 

    // Further initialization here... 
    // ... 

    // Creating server's main thread 
    m_hManagerThread = ::CreateThread(NULL, 0, CTiledLayersManager::Run, (LPVOID)this, NULL, NULL); 

我用::accept(...)等待傳入客戶端連接在CMyServer::Run(LPVOID),和在新連接被接受後,我創建一個單獨的線程CMyServer::ProcessRequest(LPVOID)以接收來自客戶端的數據,併發送一個響應作爲線程函數參數的一部分傳遞由::accept(...)返回的套接字:

DWORD CMyServer::Run(LPVOID pInstance) 
{ 
    CMyServer* pTLM = (CMyServer*)pInstance; 

    // Initialization here... 
    // ... 

    bool bContinueRun = true; 
    while (bContinueRun) 
    { 
    // Waiting for a client to connect 
    SOCKADDR clientSocketAddr;       // structure to store socket's address 
    int nClientSocketSize = sizeof(clientSocketAddr);  // defining structure's length 
    ZeroMemory(&clientSocketAddr, nClientSocketSize);  // cleaning the structure 
    SOCKET connectionSocket = ::accept(pTLM->m_serverSocket, &clientSocketAddr, &nClientSocketSize);  // waiting for client's request 
    if (connectionSocket != INVALID_SOCKET) 
    { 
     if (bContinueRun) 
     { 
     // Running a separate thread to handle this request 
     REQUEST_CONTEXT rc; 
     rc.pTLM = pTLM; 
     rc.connectionSocket = connectionSocket; 
     HANDLE hRequestThread = ::CreateThread(NULL, 0, CTiledLayersManager::ProcessRequest, (LPVOID)&rc, CREATE_SUSPENDED, NULL); 

     // Storing created thread's handle to be able to close it later 
     // ... 

     // Starting suspended thread 
     ::ResumeThread(hRequestThread); 
     } 
    } 

    // Checking whether thread is signaled to stop... 
    // ... 
    } 

    // Waiting for all child threads to over... 
    // ... 
} 

手動測試此實現爲我提供了期望的結果。但是,當我發送由JMeter生成的多個請求時,我可以看到其中一些未被DWORD CMyServer::ProcessRequest(LPVOID)正確處理。查看由ProcessRequest創建的日誌文件,我確定10038 WinSock error code(意味着::recv調用在非套接字上被嘗試過),10053錯誤代碼(軟件導致連接中止)或甚至10058錯誤代碼(套接字關閉後不能發送)。但第10038錯誤比其他人提到的更頻繁。

它看起來像是一個插座被關閉不知何故,但我只有在ProcessRequest調用::recv::send後關閉它。我也認爲這可能是一個與使用::CreateThread而不是::_beginthreadex相關的問題,但是我可以得到它只會導致內存泄漏。我沒有任何內存泄漏檢測到的方法描述here所以我懷疑它是這個原因。更重要的是,::CreateThread返回一個句柄,可用於::WaitForMultipleObjects等待線程結束,我需要它來正確地停止我的服務器。

由於客戶端不想等待響應,這些錯誤是否會發生?我沒有想法,如果你告訴我我失蹤或做錯/理解錯誤,我會感謝你。順便說一下,我的服務器和JMeter都運行在本地主機上。

最後,這裏是我的執行ProcessRequest方法:

DWORD CMyServer::ProcessRequest(LPVOID pInstance) 
{ 
    REQUEST_CONTEXT* pRC = (REQUEST_CONTEXT*)pInstance; 
    CMyServer* pTLM = pRC->pTLM; 
    SOCKET connectionSocket = pRC->connectionSocket; 

    // Retrieving client's request 
    const DWORD dwBuffLen = 1 << 15; 
    char buffer[dwBuffLen]; 
    ZeroMemory(buffer, sizeof(buffer)); 
    if (::recv(connectionSocket, buffer, sizeof(buffer), NULL) == SOCKET_ERROR) 
    { 
    stringStream ss; 
    ss << "Unable to receive client's request with the following error code " << ::WSAGetLastError() << "."; 
    pTLM->Log(ss.str(), TLM_LOG_TYPE_ERROR); 
    ::SetEvent(pTLM->m_hRequestCompleteEvent); 
    return 0; 
    } 

    string str = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nHello World!"; 
    if (::send(connectionSocket, str.c_str(), str.length(), 0) == SOCKET_ERROR) 
    { 
    stringStream ss; 
    ss << "Unable to send response to client with the following error code " << ::WSAGetLastError() << "."; 
    pTLM->Log(ss.str(), TLM_LOG_TYPE_ERROR); 
    ::SetEvent(pTLM->m_hRequestCompleteEvent); 
    return 0; 
    } 

    ::closesocket(connectionSocket); 
    connectionSocket = NULL; 

    pTLM->Log(string("Request has been successfully handled.")); 
    ::SetEvent(pTLM->m_hRequestCompleteEvent); 
    return 0; 
} 
+1

設置portnumber應該使用'htons'來代替。 –

+0

感謝您的重要修正。我想我的處理器太「幸運」了,沒有注意到'htons'和'ntohs'之間的區別。 – Ezze

回答

3

你傳遞一個指針REQUEST_CONTEXT每一個新創建的線程。但是,這是一個自動變量,分配在堆棧上。因此其使用期限僅限於其範圍。它在您撥打ResumeThread後立即結束。

實際上發生的情況是,在每次循環迭代中都使用了REQUEST_CONTEXT的相同內存。現在想象你在短時間內接受2個連接。很可能在第一個線程開始執行時,其REQUEST_CONTEXT將被覆蓋。所以你實際上有2個線程服務於同一個套接字。

最簡單的修復方法是動態分配REQUEST_CONTEXT。也就是說,在新接受時分配它,將它的指針傳遞給新線程。然後在線程終止時不要忘記delete它。

+0

你絕對是對的,親愛的瓦爾多!不考慮它太幼稚。現在它按預期工作。感謝您的新鮮觀點! – Ezze

3

創建線程來處理請求時,您將地址作爲參數提供給本地變量。只要局部變量超出範圍,該指針的數據就無效。在線程中使用newdelete動態創建它。

+0

謝謝你的回覆,你也是對的。 – Ezze