首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >WinSock2:使用recv和send在单独的线程中处理接受的传入连接

WinSock2:使用recv和send在单独的线程中处理接受的传入连接
EN

Stack Overflow用户
提问于 2011-11-17 19:41:36
回答 2查看 1.4K关注 0票数 0

我正在实现一个基于Windows的web服务器,用于处理来自使用WinSock2的客户端的多个特定HTTP请求。我有一个类来启动和停止我的服务器。它看起来像这样:

代码语言:javascript
运行
复制
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)来接受传入的连接,如下所述:

代码语言:javascript
运行
复制
  // 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(...)返回的套接字作为线程函数参数的一部分进行传递:

代码语言:javascript
运行
复制
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方法的实现:

代码语言:javascript
运行
复制
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;
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2011-11-17 19:51:03

将指向REQUEST_CONTEXT的指针传递给每个新创建的线程。但是,这是一个自动变量,在堆栈上分配。因此,它的生命周期仅限于其作用域。它会在你调用ResumeThread之后立即结束。

实际上,每次循环迭代都使用相同的REQUEST_CONTEXT内存。现在假设你在短时间内接受了2个内部连接。很可能在第一个线程开始执行的时候,它的REQUEST_CONTEXT已经被覆盖了。所以你实际上有两个线程服务于同一个套接字。

最简单的修复方法是动态分配REQUEST_CONTEXT。也就是说,在新的接受时分配它,将它的指针传递给新的线程。然后,在线程终止期间,不要忘了delete它。

票数 3
EN

Stack Overflow用户

发布于 2011-11-17 19:53:22

在创建处理请求的线程时,将地址作为局部变量的参数提供给线程。一旦局部变量超出作用域,这个指针的数据就不再有效。使用new动态创建它,并在线程中delete它。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/8166512

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档