我正在实现一个基于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 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;
}
发布于 2011-11-17 19:51:03
将指向REQUEST_CONTEXT
的指针传递给每个新创建的线程。但是,这是一个自动变量,在堆栈上分配。因此,它的生命周期仅限于其作用域。它会在你调用ResumeThread
之后立即结束。
实际上,每次循环迭代都使用相同的REQUEST_CONTEXT
内存。现在假设你在短时间内接受了2个内部连接。很可能在第一个线程开始执行的时候,它的REQUEST_CONTEXT
已经被覆盖了。所以你实际上有两个线程服务于同一个套接字。
最简单的修复方法是动态分配REQUEST_CONTEXT
。也就是说,在新的接受时分配它,将它的指针传递给新的线程。然后,在线程终止期间,不要忘了delete
它。
发布于 2011-11-17 19:53:22
在创建处理请求的线程时,将地址作为局部变量的参数提供给线程。一旦局部变量超出作用域,这个指针的数据就不再有效。使用new
动态创建它,并在线程中delete
它。
https://stackoverflow.com/questions/8166512
复制相似问题