IOCP入门

来源:互联网 发布:mac固件密码解锁 编辑:程序博客网 时间:2024/05/01 20:07
在网络通信中创建一个TCP服务器端通常是这样的:
[cpp] view plaincopy
  1. int nPort =65000;//指定通信端口  
  2.  WSADATA wsaData;  
  3. WSAStartup( MAKEWORD( 2, 2 ), &wsaData );  
  4.   
  5.  // 创建监听套接字,绑定本地端口,开始监听   
  6.  SOCKET sListen = socket( AF_INET,SOCK_STREAM, 0 );  
  7. SOCKADDR_IN addr;   
  8. addr.sin_family = AF_INET;   
  9. addr.sin_port = htons( nPort );   
  10. addr.sin_addr.S_un.S_addr = INADDR_ANY;   
  11. bind( sListen, (sockaddr *)&addr, sizeof( addr ) );   
  12. listen( sListen, 5 );  
  13.   
  14. SOCKADDR_IN saRemote;   
  15. int nRemoteLen = sizeof( saRemote );   
  16. SOCKET sRemote = accept( sListen, (sockaddr *)&saRemote, &nRemoteLen );  

         然后线程会挂起,当有客户端连上来时,accept函数会继续往下执行。这时我们就可以调用recv函数接收来自客户端的消息。当考虑到可能有多个客户端连上来时,而每个客户端又要能及时通信。我们通常做法是新开线程来处理。       

[cpp] view plaincopy
  1. while(TRUE)  
  2. {  
  3.     sRemote = accept( sListen, (sockaddr *)&saRemote, &nRemoteLen );    
  4.     HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, (void*) sRemote, 0, &dwTreadId); CloseHandle(hThread);    
  5. }  
        但如果有大量的客户端连入服务器,新开线程会消耗服务器的大量资源。于是windows提供了一种socket模型来处理这种情况,它就是IOCP,又叫端口完成。它实际上是事先开好N个线程,一个accpet线程和多个工作线程,让它们在那hold[堵塞]。一旦服务器,收到一个客户端的连接时,accept线程就会读取客户端发送的数据,并放入缓冲区。然后那多个工作线程就会从缓冲区取出数据并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源,也提高了线程的利用率。

      多个线程的关系应该是这样的。accept线程,只负责客户端的连接处理。假如:客户端C1连上服务端,accept线程,取出数据并放入缓冲区。这时工作线程开始工作,假如为worker3,worker3运行一段时间后,轮到worker1开始运行。worker1就后接手worker3的工作,继续进行。

    为了更好使用IOCP,我们要先了解几个常用的函数。

   1、CreateIoCompletionPort,首先使用这个函数创建一个Handle 用来标识一个io完成端口。然后会调用它来关联accept的套接字,和一个存在的io完成端口。

          HANDLE hIocp = CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0, 0, 0 );

         CreateIoCompletionPort( ( HANDLE)pPerHandle->s, hIocp, (DWORD)pPerHandle, 0 );

   2、WSARecv,使用这个函数来接收客户端的数据,并把数据放入缓冲区,让工作线程(事先开好的N个线程)来取。注意dwFlags通常设为0,否则会出错。

          WSARecv( pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pPerIo->ol, NULL );

   3、GetQueuedCompletionStatus,工作线程使用这个函数来获取放入缓冲区的数据。

         GetQueuedCompletionStatus( hIocp, &dwTrans, (LPDWORD)&pPerHandle, (LPOVERLAPPED*)&pPerIo, WSA_INFINITE );

   pPerHandle和pPerIo是我们自定义的数据结构,我们使用它来和工作线程进行数据交换。pPerHandle没固定要求,不过我们通常这样定义:

  typedef struct _PER_HANDLE_DATA 

    SOCKET      s;      // 对应的套接字句柄 
    sockaddr_in addr;   // 对方的地址
}PER_HANDLE_DATA, *PPER_HANDLE_DATA;

pPerIo必须包含一个OVERLAPPED的结构,我们通常这样定义:

typedef struct _PER_IO_DATA 

    OVERLAPPED ol;                 // 重叠结构 
    char        buf[BUFFER_SIZE];   // 数据缓冲区 
    int         nOperationType;     // 操作类型
#define OP_READ   1 
#define OP_WRITE 2 
#define OP_ACCEPT 3
}PER_IO_DATA, *PPER_IO_DATA;

 IOCP的TCP服务器端的源码如下:(或者见http://download.csdn.net/detail/cloud95/4183205)

注意 RECV_ONLY宏,表示服务器端只接收数据。服务端和客户端此宏的定义应保持一致。

[cpp] view plaincopy
  1. //#define RECV_ONLY  //是否只处理接收数据  
  2. #define BUFFER_SIZE 1024  
  3. /******************************************************************  
  4. * per_handle 数据  
  5. *******************************************************************/   
  6. typedef struct _PER_HANDLE_DATA   
  7. {   
  8.     SOCKET      s;      // 对应的套接字句柄   
  9.     sockaddr_in addr;   // 对方的地址  
  10. }PER_HANDLE_DATA, *PPER_HANDLE_DATA;  
  11. /******************************************************************  
  12. * per_io 数据  
  13. *******************************************************************/   
  14. typedef struct _PER_IO_DATA   
  15. {   
  16.     OVERLAPPED ol;                 // 重叠结构   
  17.     char        buf[BUFFER_SIZE];   // 数据缓冲区   
  18.     int         nOperationType;     // 操作类型  
  19. #define OP_READ   1   
  20. #define OP_WRITE 2   
  21. #define OP_ACCEPT 3  
  22. }PER_IO_DATA, *PPER_IO_DATA;  
  23.   
  24. // 唯一的应用程序对象  
  25.   
  26. CWinApp theApp;  
  27.   
  28. using namespace std;  
  29. DWORD WINAPI ServerThread( LPVOID lpParam );//工作线程  
  30. int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])  
  31. {  
  32.     int nRetCode = 0;  
  33.   
  34.     // 初始化 MFC 并在失败时显示错误  
  35.     if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))  
  36.     {  
  37.         // TODO: 更改错误代码以符合您的需要  
  38.         _tprintf(_T("错误: MFC 初始化失败\n"));  
  39.         nRetCode = 1;  
  40.     }  
  41.     else  
  42.     {  
  43.         // TODO: 在此处为应用程序的行为编写代码。  
  44.           
  45.         int nPort =65000;//指定通信端口  
  46.   
  47.         // 创建完成端口对象   
  48.         // 创建工作线程处理完成端口对象的事件   
  49.         HANDLE hIocp = CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0, 0, 0 );   
  50.   
  51.         //cpu个数*2+2 通常为最佳线程数  
  52.         SYSTEM_INFO sysInfo;  
  53.         GetSystemInfo(&sysInfo);  
  54.         int ThreadNum=sysInfo.dwNumberOfProcessors*2+2;  
  55.   
  56.         for(int i=0;i<ThreadNum;i++)  
  57.         {  
  58.             HANDLE hThread;  
  59.             hThread =CreateThread(NULL, 0, ServerThread, (LPVOID)hIocp, 0, 0);  
  60.             CloseHandle(hThread);  
  61.         }  
  62.   
  63.         WSADATA wsaData;  
  64.         WSAStartup( MAKEWORD( 2, 2 ), &wsaData );  
  65.   
  66.         // 创建监听套接字,绑定本地端口,开始监听   
  67.         SOCKET sListen = socket( AF_INET,SOCK_STREAM, 0 );  
  68.         SOCKADDR_IN addr;   
  69.         addr.sin_family = AF_INET;   
  70.         addr.sin_port = htons( nPort );   
  71.         addr.sin_addr.S_un.S_addr = INADDR_ANY;   
  72.         bind( sListen, (sockaddr *)&addr, sizeof( addr ) );   
  73.         listen( sListen, 5 );  
  74.         printf( "iocp demo start......\n" );  
  75.   
  76.         while(true)  
  77.         {  
  78.             //accept客户端  
  79.             SOCKADDR_IN saRemote;   
  80.             int nRemoteLen = sizeof( saRemote );   
  81.             SOCKET sRemote = accept( sListen, (sockaddr *)&saRemote, &nRemoteLen );  
  82.   
  83.             PPER_HANDLE_DATA pPerHandle =(PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));  
  84.             if( pPerHandle == NULL )   
  85.             {   
  86.                 break;   
  87.             }  
  88.             pPerHandle->s = sRemote;   
  89.             memcpy( &pPerHandle->addr, &saRemote, nRemoteLen );  
  90.   
  91.             //关联iocp和接收socket  
  92.             CreateIoCompletionPort( ( HANDLE)pPerHandle->s, hIocp, (DWORD)pPerHandle, 0 );  
  93.   
  94.             PPER_IO_DATA pIoData =(PPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));  
  95.   
  96.             if( pIoData == NULL )   
  97.             {   
  98.                 break;   
  99.             }  
  100.             pIoData->nOperationType = OP_READ;   
  101.             WSABUF buf;   
  102.             buf.buf = pIoData->buf;   
  103.             buf.len = BUFFER_SIZE;   
  104.   
  105.             DWORD dwRecv = 0;   
  106.             DWORD dwFlags = 0;//注意保证dwFlags为0,否则会出错  
  107.             //压缩数据到缓冲区  
  108.             WSARecv( pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pIoData->ol, NULL );  
  109.         }  
  110.     }  
  111.   
  112.     return nRetCode;  
  113. }  
  114.   
  115.   
  116. /******************************************************************  
  117. * 函数介绍:处理完成端口对象事件的线程  
  118. * 输入参数:  
  119. * 输出参数:  
  120. * 返回值 :  
  121. *******************************************************************/   
  122. DWORD WINAPI ServerThread( LPVOID lpParam )   
  123. {   
  124.     HANDLE hIocp = ( HANDLE )lpParam;   
  125.     if( hIocp == NULL )   
  126.     {   
  127.         return -1;   
  128.     }  
  129.     DWORD dwTrans = 0;   
  130.     PPER_HANDLE_DATA pPerHandle;   
  131.     PPER_IO_DATA     pPerIo;   
  132.        
  133.     while( TRUE )   
  134.     {   
  135.         // 在关联到此完成端口的所有套接字上等待I/O完成   
  136.         BOOL bRet = GetQueuedCompletionStatus( hIocp, &dwTrans, (LPDWORD)&pPerHandle, (LPOVERLAPPED*)&pPerIo, WSA_INFINITE );   
  137.         if( !bRet )     // 发生错误   
  138.         {   
  139.             closesocket( pPerHandle->s );   
  140.             GlobalFree( pPerHandle );   
  141.             GlobalFree( pPerIo );  
  142.             cout << "error" << endl;   
  143.             continue;   
  144.         }  
  145.         // 套接字被对方关闭   
  146.         if( dwTrans == 0 && ( pPerIo->nOperationType == OP_READ || pPerIo->nOperationType== OP_WRITE ) )   
  147.         {   
  148.             closesocket( pPerHandle->s );   
  149.             GlobalFree( pPerHandle );   
  150.             GlobalFree( pPerIo );  
  151.             cout << "client closed" << endl;   
  152.             continue;   
  153.         }  
  154.         switch ( pPerIo->nOperationType )   
  155.         {   
  156.         case OP_READ:       // 完成一个接收请求   
  157.             {   
  158.                 pPerIo->buf[dwTrans] = '\0';   
  159.                 printf( "%s\n", pPerIo->buf );  
  160.   
  161.                 #ifdef RECV_ONLY  
  162.                 // 继续投递接受操作                   
  163.                 WSABUF buf;   
  164.                 buf.buf = pPerIo->buf;   
  165.                 buf.len = BUFFER_SIZE;   
  166.                 pPerIo->nOperationType = OP_READ;                    
  167.                 DWORD dwRecv = 0;   
  168.                 DWORD dwFlags = 0;  
  169.                 //压缩数据到缓冲区  
  170.                 WSARecv( pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pPerIo->ol, NULL );  
  171.                 #else  
  172.                 //回应客户端  
  173.                 ZeroMemory(pPerIo->buf,BUFFER_SIZE);  
  174.                 strcpy(pPerIo->buf,"OK");  
  175.                 DWORD dwSend = 0;   
  176.                 DWORD dwFlags = 0;  
  177.                 ZeroMemory((LPVOID)&(pPerIo->ol),sizeof(OVERLAPPED));  
  178.                 WSABUF buf;   
  179.                 buf.buf = pPerIo->buf;   
  180.                 buf.len = 2;   
  181.                 pPerIo->nOperationType = OP_WRITE;                 
  182.                 //发送数据  
  183.                 WSASend(pPerHandle->s,&buf,1,&dwSend,dwFlags,&pPerIo->ol,NULL);                 
  184.                 #endif                
  185.             }   
  186.             break;   
  187.         case OP_WRITE:   
  188.             {  
  189.                 #ifdef RECV_ONLY  
  190.                  
  191.                 #else  
  192.                 //发送时的处理  
  193.   
  194.                 // 继续投递接受操作                   
  195.                 WSABUF buf;   
  196.                 buf.buf = pPerIo->buf;   
  197.                 buf.len = BUFFER_SIZE;   
  198.                 pPerIo->nOperationType = OP_READ;                    
  199.                 DWORD dwRecv = 0;   
  200.                 DWORD dwFlags = 0;  
  201.                 //压缩数据到缓冲区  
  202.                 WSARecv( pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pPerIo->ol, NULL );  
  203.                 #endif            
  204.             }  
  205.             break;  
  206.         case OP_ACCEPT:   
  207.             break;  
  208.         }  
  209.     }  
  210.     return 0;   
  211. }  

原帖地址: http://blog.csdn.net/cloud95/article/details/7406128
原创粉丝点击