我正在實施基於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;
}
設置portnumber應該使用'htons'來代替。 –
感謝您的重要修正。我想我的處理器太「幸運」了,沒有注意到'htons'和'ntohs'之間的區別。 – Ezze