0

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)。によって作成されたログ ファイルを見ると、ProcessRequest10038 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;
}
4

2 に答える 2

3

REQUEST_CONTEXT新しく作成されたすべてのスレッドにポインターを渡します。ただし、これはstackに割り当てられる自動変数です。したがって、その寿命はその範囲に制限されます。を呼び出したらすぐに終了しますResumeThread

実際にREQUEST_CONTEXTは、すべてのループ反復で同じメモリが使用されます。ここで、内部で短時間に 2 つの接続を受け入れるとします。最初のスレッドが実行を開始した時点で、REQUEST_CONTEXTすでに上書きされている可能性があります。実際には、同じソケットにサービスを提供する 2 つのスレッドがあるようにします。

最も簡単な修正は、REQUEST_CONTEXT動的に割り当てることです。つまり、新しい受け入れ時に割り当て、そのポインターを新しいスレッドに渡します。次に、スレッドの終了時にそれを忘れないでくださいdelete

于 2011-11-17T11:51:03.110 に答える
3

リクエストを処理するスレッドを作成するときは、ローカル変数にアドレスを引数としてスレッドに渡します。このポインターのデータは、ローカル変数がスコープ外になるとすぐに無効になります。newdeleteスレッドで動的に作成します。

于 2011-11-17T11:53:21.523 に答える