研究了几天IOCP的模型,参考了很多大牛的文章,有了点自己的感觉,于是把心得写下来,算是记录一下成长的过程,也希望用最直观的图的形式让以后的人更直观的明白IOCP的基本原理,其实IOCP有很多实现的方式,先说一下第一种比较基本的ICOP,这种理解之后其他的就很容易理解了。:)
先看一下我绘制的一个原理图,图应该更容易理解一下,绘制了1个多小时呢,水平有限,高手莫怪。
流程图解释————
标红的①②③④就是完成一个IOCP模型的时间构建的顺序,基本是代码的执行顺序。
①:创建一个完成端口,也就是创建一个系统维护的消息队列
②:创建IO线程池,数目基本是2*CPU数目,工作线程主要完成如下逻辑:接受client的连接请求,接受数据,客户端断开连接,主动终止本工作线程(WorkFun).
③: 使用方法CreateIoCompletionPort将SRV端的监听套接字(svrSock)和等待接受的套接字(acceptSock)绑定到完成端口,也就是把把socket扔进线程池中。接受操作系统对监听套接字(svrSock)和等待接受的套接字(acceptSock)的完成状态的监听(状态如下:接受数据,客户端断开连接,主动终止)
④:系统开始了对已经绑定的socket的监听。如果有IO的完成通知的话,IO完成消息队列中就会增加一条记录(是系统push的), 消息队列中的消息记录会被系统自动的安全的分发到不同的工作线程中去,一条记录只分给一个线程,分发完成后此记录就被系统从IO完成消息队列中删除;若有新的记录进来继续分发,直到IO完成消息队列中没有任何的IO完成通知消息.
WorkFun领到一条消息后便进行逻辑处理和判断,根据消息的类型进行不的逻辑操作,例如:收到数据接收完成的通知就WSARecv一下,根据具体的数据内容进行你的项目的业务逻辑。
贴出部分的代码————
工作线程函数
1
/*
* Completion Port工作线程函数
2 * \param void * pThreadData
3 * \return unsigned int 函数结束标志
4 */
5 unsigned int CALLBACK CIoWkThread::WorkFun( void * pThreadData)
6 { ...
7 while ( true )
8 {
9 ...
10 switch (pExOL -> m_tiTransInfo.m_nOpType)
11 {
12 case OP_ACCEPT:
13 {
14 ...
15 }
16 break ;
17 case OP_READ:
18 {
19 ...
20 }
21 break ;
22 case OP_DISCONNECT:
23 {
24 ...
25
26 BOOL BRet = DisconnectEx(pExOL -> m_tiTransInfo.m_skAccept,
27 (LPOVERLAPPED)pExOL,
28 TF_REUSE_SOCKET,
29 0 );
30
31 // 重新绑定一个“可重用的AcceptSock”
32 HANDLE hdRet = CreateIoCompletionPort((HANDLE)pExOL -> m_tiTransInfo.m_skAccept,
33 hPort,
34 (ULONG_PTR)pExOL,
35 0 );
36 // 1.AcceptEx可能会其内部创建线程来等待一个连接过来的可用的AcceptSock,没有的话,会在内部等待,有的话就会生成一个可通信用的AcceptSock
37 // 2.AcceptEx函数返回了,不代表客户端连接进来或者连接成功了,我们必须依靠它的“完成通知”才能知道这个事实
38 AcceptEx(pExOL -> m_tiTransInfo.m_skServer,
39 pExOL -> m_tiTransInfo.m_skAccept,
40 pExOL -> m_tiTransInfo.m_pBuf,
41 0 ,
42 sizeof (sockaddr_in) + 16 ,
43 sizeof (sockaddr_in) + 16 ,
44 NULL,
45 (LPOVERLAPPED)pExOL);
46 }
47 break ;
48 case OP_TERMINATE:
49 bIsTerminate = true ;
50 break ;
51 default :
52 break ;
53 }
54
55 // 接收到的数据长度清0
56 dwNumOfBytes = 0 ;
57 // 连接Socket结束
58 if (bIsTerminate)
59 {
60 break ;
61 }
62 }
63
64 // TODO:考虑线程安全
65 if (NULL != upCompletionKey)
66 {
67 delete ((PTRANS_INFO)upCompletionKey);
68 upCompletionKey = NULL;
69 }
70
71 return 0 ;
72 }
2 * \param void * pThreadData
3 * \return unsigned int 函数结束标志
4 */
5 unsigned int CALLBACK CIoWkThread::WorkFun( void * pThreadData)
6 { ...
7 while ( true )
8 {
9 ...
10 switch (pExOL -> m_tiTransInfo.m_nOpType)
11 {
12 case OP_ACCEPT:
13 {
14 ...
15 }
16 break ;
17 case OP_READ:
18 {
19 ...
20 }
21 break ;
22 case OP_DISCONNECT:
23 {
24 ...
25
26 BOOL BRet = DisconnectEx(pExOL -> m_tiTransInfo.m_skAccept,
27 (LPOVERLAPPED)pExOL,
28 TF_REUSE_SOCKET,
29 0 );
30
31 // 重新绑定一个“可重用的AcceptSock”
32 HANDLE hdRet = CreateIoCompletionPort((HANDLE)pExOL -> m_tiTransInfo.m_skAccept,
33 hPort,
34 (ULONG_PTR)pExOL,
35 0 );
36 // 1.AcceptEx可能会其内部创建线程来等待一个连接过来的可用的AcceptSock,没有的话,会在内部等待,有的话就会生成一个可通信用的AcceptSock
37 // 2.AcceptEx函数返回了,不代表客户端连接进来或者连接成功了,我们必须依靠它的“完成通知”才能知道这个事实
38 AcceptEx(pExOL -> m_tiTransInfo.m_skServer,
39 pExOL -> m_tiTransInfo.m_skAccept,
40 pExOL -> m_tiTransInfo.m_pBuf,
41 0 ,
42 sizeof (sockaddr_in) + 16 ,
43 sizeof (sockaddr_in) + 16 ,
44 NULL,
45 (LPOVERLAPPED)pExOL);
46 }
47 break ;
48 case OP_TERMINATE:
49 bIsTerminate = true ;
50 break ;
51 default :
52 break ;
53 }
54
55 // 接收到的数据长度清0
56 dwNumOfBytes = 0 ;
57 // 连接Socket结束
58 if (bIsTerminate)
59 {
60 break ;
61 }
62 }
63
64 // TODO:考虑线程安全
65 if (NULL != upCompletionKey)
66 {
67 delete ((PTRANS_INFO)upCompletionKey);
68 upCompletionKey = NULL;
69 }
70
71 return 0 ;
72 }
调用的方法
代码
1
POVERLAPPED_MY pMyOL
=
new
POVERLAPPED_MY;
//
POVERLAPPED_MY自己定义结构
2 HANDLE hIocp = INVALID_HANDLE_VALUE; // 创建Completion Port
3 hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL, 0 );
4
5 CreateIoWkThread(); // CreateIoWkThread调用WorkFun产生线程池
6 SOCKET skSvr = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0 , WSA_FLAG_OVERLAPPED); // 服务器端监听Socket
7 // TODO ERR判断
8
9 // 服务器端连接Socket
10 SOCKET skAccept = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0 , WSA_FLAG_OVERLAPPED);
11
12
13 // 需要同时‘监控’skSvr和skAccept,因为客户端第一次conn的时候,完成通知是针对skSvr;
14 // 但是send数据的时候,完成通知是针对skAccept;
15 CreateIoCompletionPort((HANDLE)skSvr, hIocp,(ULONG_PTR)pMyOL, 0 );
16 // TODO ERR判断
17 CreateIoCompletionPort((HANDLE)skAccept,hIocp,(ULONG_PTR)pMyOL, 0 );
18 // TODO ERR判断
19
20 SOCKADDR_IN addr; // 需要绑定的参数
21 addr.sin_family = AF_INET;
22 addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // ip地址
23 addr.sin_port = htons( 6200 ); // 绑定端口
24 bind(skSvr,(SOCKADDR * ) & addr, sizeof (SOCKADDR)); // 绑定完成
25 listen(skSvr, 5 ); // 其中第二个参数代表能够接收的最多的连接数
26 int a = 111 ;
27
28 // 1.AcceptEx可能会其内部创建线程来等待一个连接过来的可用的AcceptSock,没有的话,会在内部等待,有的话就会生成一个可通信用的AcceptSock
29 // 2.AcceptEx函数返回了,不代表客户端连接进来或者连接成功了,我们必须依靠它的“完成通知”才能知道这个事实
30 AcceptEx(skSvr,
31 skAccept,
32 pMyOL -> m_tiTransInfo.m_pBuf,
33 0 , // 这个是必须的0
34 sizeof (sockaddr_in) + 16 ,
35 sizeof (sockaddr_in) + 16 ,
36 NULL,
37 (LPOVERLAPPED)pMyOL);}
2 HANDLE hIocp = INVALID_HANDLE_VALUE; // 创建Completion Port
3 hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL, 0 );
4
5 CreateIoWkThread(); // CreateIoWkThread调用WorkFun产生线程池
6 SOCKET skSvr = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0 , WSA_FLAG_OVERLAPPED); // 服务器端监听Socket
7 // TODO ERR判断
8
9 // 服务器端连接Socket
10 SOCKET skAccept = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0 , WSA_FLAG_OVERLAPPED);
11
12
13 // 需要同时‘监控’skSvr和skAccept,因为客户端第一次conn的时候,完成通知是针对skSvr;
14 // 但是send数据的时候,完成通知是针对skAccept;
15 CreateIoCompletionPort((HANDLE)skSvr, hIocp,(ULONG_PTR)pMyOL, 0 );
16 // TODO ERR判断
17 CreateIoCompletionPort((HANDLE)skAccept,hIocp,(ULONG_PTR)pMyOL, 0 );
18 // TODO ERR判断
19
20 SOCKADDR_IN addr; // 需要绑定的参数
21 addr.sin_family = AF_INET;
22 addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // ip地址
23 addr.sin_port = htons( 6200 ); // 绑定端口
24 bind(skSvr,(SOCKADDR * ) & addr, sizeof (SOCKADDR)); // 绑定完成
25 listen(skSvr, 5 ); // 其中第二个参数代表能够接收的最多的连接数
26 int a = 111 ;
27
28 // 1.AcceptEx可能会其内部创建线程来等待一个连接过来的可用的AcceptSock,没有的话,会在内部等待,有的话就会生成一个可通信用的AcceptSock
29 // 2.AcceptEx函数返回了,不代表客户端连接进来或者连接成功了,我们必须依靠它的“完成通知”才能知道这个事实
30 AcceptEx(skSvr,
31 skAccept,
32 pMyOL -> m_tiTransInfo.m_pBuf,
33 0 , // 这个是必须的0
34 sizeof (sockaddr_in) + 16 ,
35 sizeof (sockaddr_in) + 16 ,
36 NULL,
37 (LPOVERLAPPED)pMyOL);}