WinSock2を使用して、クライアントからの複数の特定の HTTP 要求を処理する Windows ベースの Web サーバーを実装しています。サーバーを起動および停止するクラスがあります。次のようになります。
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によって生成された複数のリクエストを送信すると、そのうちのいくつかが .NET によって適切に処理されていないことがわかりますDWORD CMyServer::ProcessRequest(LPVOID)
。によって作成されたログ ファイルを見ると、ProcessRequest
10038 WinSock エラー コード(非ソケットで呼び出しが試行されたことを意味する::recv
)、10053 エラー コード (ソフトウェアが原因で接続が中止された)、さらには 10058 エラー コード (ソケットのシャットダウン後に送信できません) を特定できます。しかし、10038 番目のエラーは、他の人が言及したよりも頻繁に発生します。
ソケットが何らかの理由で閉じられたように見えますが、後で閉じて、 で::recv
呼び出さ::send
れましたProcessRequest
。また、 ::_beginthreadexの代わりに::CreateThreadを使用することに関連する問題である可能性があると考えましたが、メモリリークにつながる可能性があるだけです。ここで説明されている方法でメモリリークが検出されていないため、それが理由であることに疑いがあります。さらに、スレッドが終了するのを待つために::WaitForMultipleObjectsで使用できるハンドルを返します。これは、サーバーを適切に停止するために必要です。::CreateThread
これらのエラーは、クライアントが応答を待ちたくないために発生する可能性がありますか? 私はアイデアがありません。私が見逃していること、または行っていること/理解が間違っていることを教えていただければ幸いです。ちなみに、サーバーも 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;
}