宁静以致远
zgf的blog
<2009年3月>
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

留言簿(17)

随笔分类

随笔档案

文章档案

友情链接

资料收藏

搜索

最新评论

阅读排行榜

评论排行榜

 
VC知识库BLOG   首页  新随笔  联系  聚合  登录 
  随笔-37 文章-8 评论-121 Trackbacks-0
     前几天同事用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准备下一个连接。这样可以避免平凡的分配和释放内存。
posted on 2009-03-12 00:01 zgf的blog 阅读(2193) 评论(4)  编辑 收藏
Comments
  • # re: 面向短连接的网络服务器
    超赞链链网
    Posted @ 2009-03-12 15:54
    抢占沙发,支持。
  • # re: 面向短连接的网络服务器
    David
    Posted @ 2009-03-14 12:15
    在server初始化的时候先投递一批accept到IOCP队列中,然后IOCP响应accept后,在得到client 发起的socket句柄后,立刻再次投递accept到IOCP中,保证IOCP中有一定数目的accept在等待。同时开一个listen thread,监听FD_ACCEPT事件。
    int CTcpServer::IssueAcceptOperation()
    {
    try
    {
    int errorCode = 1;
    unsigned long dwBytes = 0;
    unsigned long dwAcceptNumbs = 0;

    SOCKET hClientSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (INVALID_SOCKET == hClientSocket)
    {
    CloseClientSocket(hClientSocket);
    throw CSrvException("PostAcceptOperation()->WSASocket(),Invalid socket handle.",-1,(long)(__LINE__));
    }
    //设置为异步模式
    ULONG ul = 1;
    errorCode =ioctlsocket(hClientSocket, FIONBIO, &ul);
    if(SOCKET_ERROR == errorCode)
    {
    CloseClientSocket(hClientSocket);
    throw CSrvException("PostAcceptOperation()->ioctlsocket(),incorrect ioctlsocket operation.",-1,(long)(__LINE__));
    }
    CAcceptContext* pAccContext = CAcceptContext::GetContext();
    if (NULL == pAccContext)
    {
    CloseClientSocket(hClientSocket);
    throw CSrvException("PostAcceptOperation()->CAcceptContext::GetContext(),Invalid AcceptContext handle.",-1,(long)(__LINE__));
    }
    pAccContext->SetAcceptParameters(this->m_hListenSocket,hClientSocket);

    errorCode = m_pfAcceptEx(pAccContext->m_struListenSocket,pAccContext->m_hSocket, pAccContext->m_ucAddressbuf,0,ACCEPT_ADDRESS_LENGTH, ACCEPT_ADDRESS_LENGTH, &dwBytes, &(pAccContext->m_struOperateOl));
    if(FALSE == errorCode && ERROR_IO_PENDING != WSAGetLastError())
    {
    CloseClientSocket(hClientSocket);
    pAccContext->ReleaseContext();
    throw CSrvException("PostAcceptOperation()->AcceptEx,incorrect AcceptEx operation.",-1,(long)(__LINE__));
    }
    return 1;
    }
    catch(CSrvException& e)
    {
    m_pLogFile->LogEx("%s at line:%d\n",e.GetExpDescription(),e.GetExpLine());
    return 0;
    }
    }
  • # re: 面向短连接的网络服务器
    David
    Posted @ 2009-03-14 12:19
    在你的代码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);
            }

    在你的这段代码中调用我上面的函数即可。
  • # re: 面向短连接的网络服务器
    Roy T.Burns
    Posted @ 2009-11-12 14:28
    我的IOCP有内存泄露,可以帮我看看吗?
    qq:350514508
标题  
姓名  
主页
验证码 *
内容   
  登录  使用高级评论  Top
[使用Ctrl+Enter键可以直接提交]