Я реализую веб-сервер на базе Windows, обрабатывающий несколько конкретных HTTP-запросов от клиентов, использующих WinSock2 .У меня есть класс для запуска и остановки моего сервера.Это выглядит примерно так:
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-я ошибка возникает чаще, чем упоминалось другими.
Похоже, сокет как-то был закрыт, но я закрываю его только после вызова ::recv
и ::send
в ProcessRequest
.Я также подумал, что это может быть проблема, связанная с использованием :: 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 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;
}