WinSock2:使用recv和send在单独的线程中处理接受的传入连接
WinSock2: handling accepted incoming connections in separate threads with recv and send
我正在实现一个基于windows的web服务器,使用WinSock2处理来自客户端的多个特定HTTP请求。我有一个类来启动和停止我的服务器。它看起来像这样:
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错误代码(意味着::recv
调用在非套接字上尝试),10053错误代码(软件导致连接中止)甚至10058错误代码(套接字关闭后无法发送)。但是第10038个错误比前面提到的其他错误发生得更频繁。
它看起来像是一个套接字被关闭了,但我只在ProcessRequest
中调用::recv
和::send
之后才关闭它。我还认为这可能是一个与使用::CreateThread而不是::_beginthreadex有关的问题,但我可以得到它只会导致内存泄漏。我没有通过这里描述的方法检测到任何内存泄漏,所以我怀疑这是原因。更重要的是,::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 OKnContent-Type: text/plainnnHello 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;
}
将指向REQUEST_CONTEXT
的指针传递给每个新创建的线程。然而,这是一个自动变量,在堆栈上分配。因此,它的生命周期受限于它的范围。在你调用ResumeThread
之后,它就结束了。
实际上发生的是REQUEST_CONTEXT
在每次循环迭代中使用相同的内存。现在想象一下你在短时间内接受了2个连接。很可能在第一个线程开始执行时,它的REQUEST_CONTEXT
已经被覆盖了。这样你实际上有两个线程服务于同一个套接字。
最简单的修复是动态分配REQUEST_CONTEXT
。也就是说,在新接受时分配它,将它的指针传递给新线程。然后在线程终止时不要忘记delete
。
当创建线程来处理请求时,您将地址作为线程的参数提供给本地变量。一旦局部变量超出作用域,该指针的数据将不再有效。在线程中使用new
和delete
动态创建它
- 某些 boost::asio 异步函数是否将处理程序连接到操作,以便处理程序被触发一次?
- 使用单个套接字处理多个传入的 UDP 连接
- 使用 QThreadPool 处理多个连接
- 如何通过解析缓冲区并将数据放入正确的结构来处理传入的数据包连接?
- 如何处理在 CPP 中连接到服务器的多个客户端?
- 多螺纹卷发同时处理多个连接
- boost::asio 允许非阻塞接受新连接,而连接的处理程序正在阻塞
- Qt的事件循环线程是安全的还是原子的?处理"队列连接"时如何同步?
- 如何使用 lamba 作为连接处理程序调用 boost async_connect 作为成员函数
- Qt-同时处理多个数据库连接
- 如何处理 KDE5 等离子体中第二个显示器的连接
- 如何在删除或处理文件夹树时处理符号链接和连接
- 处理客户端连接的最有效方法(套接字编程)
- 在Windows下处理多个客户端连接的最佳方法(不使用线程)
- 加速和 Windows 套接字 - 正确处理 TCP 客户端断开连接方案
- 处理"非阻塞"套接字连接
- Qt 错误:信号 QDeclarativeEngine::quit() 发出,但没有连接接收器来处理它
- 在没有连接的情况下调用C++Boost异步服务器处理程序
- MongoDB C++驱动程序处理副本集连接故障
- 处理连接对象时出现分段错误