完成端口cookbook

在服务器开发上虚度日月久矣,每次新的开发都要重新写一遍netlayer,厌烦了这种事情想做一个类库给自己的时候,一直使用完成端口做网络层没有错,但是不去深刻理解完成端口的本质就不对了,而且在该使用这个强力工具的时候没有想起使用却是大大的错了。

  cookbook只讲step by step创建一个完成端口模块(网络)。

  意识到线程切换的巨大代价,NT小组开发了完成端口这个内核级的东西。我们平时使用比较多的是如下三个API:

[csharp] view plain copy print ?
  1. WINBASEAPI  
  2.   
  3. __out  
  4.   
  5. HANDLE  
  6.   
  7. WINAPI  
  8.   
  9. CreateIoCompletionPort(  
  10.   
  11.     __in     HANDLE FileHandle,  
  12.   
  13.     __in_opt HANDLE ExistingCompletionPort,  
  14.   
  15.     __in     ULONG_PTR CompletionKey,  
  16.   
  17.     __in     DWORD NumberOfConcurrentThreads  
  18.   
  19.     );  
  20.   
  21.   
  22.   
  23. WINBASEAPI  
  24.   
  25. BOOL  
  26.   
  27. WINAPI  
  28.   
  29. GetQueuedCompletionStatus(  
  30.   
  31.     __in  HANDLE CompletionPort,  
  32.   
  33.     __out LPDWORD lpNumberOfBytesTransferred,  
  34.   
  35.     __out PULONG_PTR lpCompletionKey,  
  36.   
  37.     __out LPOVERLAPPED *lpOverlapped,  
  38.   
  39.     __in  DWORD dwMilliseconds  
  40.   
  41.     );  
  42.   
  43.   
  44.   
  45. WINBASEAPI  
  46.   
  47. BOOL  
  48.   
  49. WINAPI  
  50.   
  51. PostQueuedCompletionStatus(  
  52.   
  53.     __in     HANDLE CompletionPort,  
  54.   
  55.     __in     DWORD dwNumberOfBytesTransferred,  
  56.   
  57.     __in     ULONG_PTR dwCompletionKey,  
  58.   
  59.     __in_opt LPOVERLAPPED lpOverlapped  
  60.   
  61.     );  

  时间顺序上,完成端口可以理解为,首先我们告诉操作系统要做一件事情( CreateIoCompletionPort),为了获取该事情的处理结果或者结果数据,我们使用GetQueuedCompletionStatus阻塞等待操作的完成,操作系统做完指定的任务后,通过PostQueuedCompletionStatus方法返回给我们定制的信息。

  这只是时间顺序上的逻辑模拟解释,操作系统做些事情的时候要做的东西我就不知道了。既然如此,那开发一个基于完成端口的网络模块应该有如下步骤:

  1.初始化winsock2库。

  这个老生常谈了,但是我在开发的时候还是往往忘了这个,调试异常的时候才意识到忘了初始化环境了。在开发网络程序的时候,我们总要使用这样的函数对:

[csharp] view plain copy print ?
  1. int _tmain(int argc, _TCHAR* argv[])  
  2.   
  3. {  
  4.   
  5.     WSAData wd;  
  6.   
  7.     WSAStartup(MAKEWORD(2,2),&wd);  //初始化   
  8.   
  9.     //your code   
  10.   
  11.     WSACleanup();                   //清理   
  12.   
  13.     return 0;  
  14.   
  15. }  

  2.初始化监听socket相关(初始化服务器环境)

  网络编程非常常见的东西:

[csharp] view plain copy print ?
  1. m_ssock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);  
  2.   
  3. sockaddr_in saddr;  
  4.   
  5. saddr.sin_addr.s_addr    = INADDR_ANY;  
  6.   
  7. saddr.sin_family        = AF_INET;  
  8.   
  9. saddr.sin_port          = htons(2350);  
  10.   
  11. bind(m_ssock,(sockaddr*)&saddr,sizeof(saddr));  
  12.   
  13. listen(m_ssock,5);  

  3.把服务器socket绑定在完成端口上

  这涉及完成端口句柄的创建以及socket句柄的绑定。当把一个完成句柄绑定在完成端口句柄上的时候,我们可以传递一个CompletionKey参数给CreateIoCompletionPort函数,以后GetQueuedCompletionStatus方法可以原样获取这个参数,也就是说,我们可以给特定句柄绑定一份数据,以后该句柄被GetQueuedCompletionStatus方法查询到的时候都可以获取这份数据,我看过不少代码把这个东西叫做perHandleData。

  基于传递perHandleData的需求,我们可以定义一个结构。

[csharp] view plain copy print ?
  1. struct perHandleData   
  2.   
  3. {  
  4.   
  5.     union  
  6.   
  7.     {  
  8.   
  9.         SOCKET  sock;  
  10.   
  11.         HANDLE  handle;  
  12.   
  13.     };  
  14.   
  15.     int ty;     //sock=1,handle=2   
  16.   
  17. };  
  18.   
  19.   
  20.   
  21.     //开辟完成端口和线程   
  22.   
  23.     m_hiocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,0);  
  24.   
  25.   
  26.   
  27.     perHandleData*  svrhandle   = m_handleset.getnew();  
  28.   
  29.     svrhandle->sock  = m_ssock;  
  30.   
  31.     svrhandle->ty    = 1;  
  32.   
  33.   
  34.   
  35.     CreateIoCompletionPort(m_ssock,m_hiocp,ULONG_PTR(svrhandle),0);  

  3.创建工作者线程

  在异步模型中,总有线程是在等待的,完成端口模型提高效率的方式是操作系统管理等待,对于我们的程序而言只是简单的调用GetQueuedCompletionStatus方法等待,在有消息的时候操作系统会告诉我们。

[csharp] view plain copy print ?
  1.     for ( int i = 0;i < MAX_WORKER; ++i )  
  2.   
  3.     {  
  4.   
  5.         m_hworkerset[i] = CreateThread(NULL,0,worker,(LPVOID)this,0,NULL);  
  6.   
  7.     }  
  8.   
  9.   
  10.   
  11. DWORD   WINAPI iocp::worker( LPVOID lpthis )  
  12.   
  13. {  
  14.   
  15.     iocp* pthis = static_cast<iocp*>(lpthis);  
  16.   
  17.     BOOL    bgqcp;  
  18.   
  19.     DWORD   transed;  
  20.   
  21.     perIoData*  piodata;  
  22.   
  23.     perHandleData*  phddata;  
  24.   
  25.     while (true)  
  26.   
  27.     {  
  28.   
  29.         transed = 0;  
  30.   
  31.         piodata = NULL;  
  32.   
  33.         bgqcp   = GetQueuedCompletionStatus(pthis->m_hiocp,&transed,(PULONG_PTR)&phddata,(LPOVERLAPPED*)&piodata,INFINITE);  
  34.   
  35.         if (!bgqcp)  
  36.   
  37.         {  
  38.   
  39.             continue;  
  40.   
  41.         }  
  42.   
  43.         //your process   
  44.   
  45.     }  
  46.   
  47.     return  0L;  
  48.   
  49. }  

  4.GetQueuedCompletionStatus得到信息并处理

  从GetQueuedCompletionStatus中,我们得到了先前交给CreateIoCompletionPort的perHandleData数据,通同时我们还可以通过out指针得到一个指向OVERLAPPED结构的指针,利用struct的内存布局,我们可以使用这个指针传递自定义信息。完成端口每次io操作完毕后我们都可以获得信息,很多代码里把这份信息称为perIoData。

[csharp] view plain copy print ?
  1. struct perIoData   
  2.   
  3. {  
  4.   
  5.     WSAOVERLAPPED   ov;  
  6.   
  7.     IOOperation     op;  
  8.   
  9.     LPVOID          data;  
  10.   
  11.     perIoData() :data(NULL){}  
  12.   
  13.     ~perIoData(){if(data)delete data;}  
  14.   
  15. };  

  5.如何把perIoData交给操作系统

  perIoData在完成端口模型中是传递数据的核心,mswsock天生和完成端口结合在了一起,所以在完成端口的网络编程中,传递参数的隐晦性是学习的难点。其实WSARecv、WSASend、AcceptEx都可以传递perIoData。也就是说,只要再进行简单的预处理,完成端口的网络模型就可以工作起来拉。

你可能感兴趣的:(完成端口cookbook)