面向短连接的网络服务器
2009-04-17 20:03:41 来源:WEB开发网前几天同事用sf上的一个网络类库写了个服务器。一测试发现性能很差。最多每秒才能处理500次请求,并且是在网络很好的情况下,隔两个交换机后客户端就只能收到200次/秒的正确响应了。同事忙着做其它事,改进服务器的任务就交给我了。
项目中客户端的请求仅是有20bytes的数据,并且只有一小部分需要服务器回复500bytes左右的数据。综合考虑各种网络模型后我决得IOCP模型更适合当前的应用。
IOCP模型的使用方法很多资料都有。《windows网络编程》(第二版)讲得很好。随书光盘中有使用IOCP模型的简单但很好的例子。我的服务器程序写完时还没看到这个示例,正被其它示例代码里的锁弄的晕头转向。当看到这个示例后发现那些锁都是多余的。剩下的代码基本上差不多。
有点不同的是PER_IO_DATA的处理上。所有的例子在往完成端口投递请求时都先分配了一个PER_IO_DATA,请求处理后马上释放。考虑到我的应用中所有请求全部是短连接,并且数据量很小,我觉得分配和释放是浪费的。每个PER_IO_DATA对应一个socket,当socket关闭后这个PER_IO_DATA不必释放,而是用来准备接受下一个连接socket。
下面是我写的工作线程代码:
DWORD WINAPI ServerWorkerThread(LPVOID lpparam)
{
CIocpServer * pServer = (CIocpServer*)lpparam;
DWORD BytesTransferred;
SOCKET socket;
LPPER_IO_OPERATION_DATA PerIoData;
BOOL close_socket = FALSE;
int ret;
while(pServer->bRun)
{
ret = GetQueuedCompletionStatus(pServer->CompletionPort, &BytesTransferred,
(LPDWORD)&socket, (LPOVERLAPPED *) &PerIoData, INFINITE);
if (ret == ERROR_SUCCESS)
{
DWORD last_error = GetLastError();
if(last_error == ERROR_SUCCESS)
return 0; //完成端口被关闭,退出
if(ERROR_NETNAME_DELETED == last_error
|| ERROR_OPERATION_ABORTED == last_error)
close_socket = TRUE; //socket被关闭 或者 操作被取消
else
continue;
}
if (BytesTransferred == 0)
{
if(socket == 0 && PerIoData == 0)
break;
closesocket(PerIoData->socket);
pServer->Accept(PerIoData);
continue;
}
if(PerIoData->eType == IO_EVENT_ACCEPT)
{
setsockopt(PerIoData->socket,SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)&(pServer->m_server), sizeof(pServer->m_server) ) ;
if(CreateIoCompletionPort((HANDLE) PerIoData->socket, pServer->CompletionPort, (DWORD) PerIoData->socket,0) == NULL)
printf("CreateIoCompletionPort error:%d ", GetLastError());
ret = pServer->Recv(PerIoData,BytesTransferred);
}
else if(PerIoData->eType == IO_EVENT_WSARECV)
{
ret = pServer->Recv(PerIoData,BytesTransferred);
}
else if(PerIoData->eType == IO_EVENT_WSASEND)
{
ret = pServer->Send(PerIoData,BytesTransferred);
}
if(ret == FALSE)
{
closesocket(PerIoData->socket);
pServer->Accept(PerIoData);
}
}
return 0;
}
可以看到,每个AccepteEx接入的客户端连接对应着一个PER_IO_DATA,并伴随着该连接的整个生命周期。当连接结束后,PER_IO_DATA马上又通过投递AccepteEx准备下一个连接。这样可以避免平凡的分配和释放内存。
更多精彩
赞助商链接