C++Socket编程—socket网络模型之IOCP

网络模型—IOCP模型

一.  什么是IOCP?什么是IOCP模型?IOCP模型有什么作用?
      1) IOCP(I/O Completion Port),常称I/O完成端口。
      2) IOCP模型属于一种通讯模型,适用于(能控制并发执行的)高负载服务器的一个技术,适用于大型项目,处理高并发问题。
      3) 通俗一点说,就是用于高效处理很多很多的客户端进行数据交换的一个模型。
     4) 或者可以说,就是能异步I/O操作的模型。

二.  IOCP 工作机制
    尽管select、WSAA、WSAE这些socket通信模型可以让我们不用开
更多的线程来处理每一连接,但它们收发数据时仍然要调用Recv和Send,Recv和Send实际上仍然会与操作系统底层进行交互,仍然会进入内核,所以还是会有效率上的损失。
    IOCP怎么解决这个问题呢?IOCP有一个队列,当你要发数据时,收数据和连接时,都交由IOCP队列处理,不会与操作系统底层交互。

 

 

 

  发送数据时,先将缓冲区和长度封好,这个请求会发送到IOCP队列,IOCP内部会帮你把请求发出去。
收数据时,收数据的请求丢掉IOCP队列,IOCP会将收到的数据填入指定的缓冲区里边,当数据收好后会通知你来收数据。
建立连接时,IOCP帮你把连接建立好,告诉你新的连接已经来了。
  开发者使用IOCP时无需关注数据收、发、连接,只需关注处理数据


三. IOCP的存在理由(IOCP的优点)及技术相关有哪些?
      IOCP是用于高效处理很多很多的客户端进行数据交换的一个模型,那么,它具体的优点有些什么呢?它到底用到了哪些技术了呢?在Windows环境下又如何去使用这些技术来编程呢?它主要使用上哪些API函数呢?
 
1) 使用IOCP模型编程的优点
       ① 帮助维持重复使用的内存池。(与重叠I/O技术有关)
       ② 去除删除线程创建/终结负担。
       ③ 利于管理,分配线程,控制并发,最小化的线程上下文切换。
       ④ 优化线程调度,提高CPU和内存缓冲的命中率。

2) 使用IOCP模型编程汲及到的知识点
       ① 同步与异步
       ② 阻塞与非阻塞
       ③ 重叠I/O技术
       ④ 多线程
       ⑤ 栈、队列这两种基本的数据结构

3) 需要使用上的API函数
  ① 与SOCKET相关
       1、链接套接字动态链接库:int WSAStartup(...);
       2、创建套接字库:        SOCKET socket(...);
       3、绑字套接字:          int bind(...);
       4、套接字设为监听状态: int listen(...);
       5、接收套接字:          SOCKET accept(...);
       6、向指定套接字发送信息:int send(...);
       7、从指定套接字接收信息:int recv(...);

  ② 与线程相关
       1、创建线程:HANDLE CreateThread(...);

  ③ 重叠I/O技术相关
       1、向套接字发送数据:    int WSASend(...);
       2、向套接字发送数据包:  int WSASendFrom(...);
       3、从套接字接收数据:    int WSARecv(...);
       4、从套接字接收数据包:  int WSARecvFrom(...);

  ④ IOCP相关
        1、创建ICOCP对象: HANDLE WINAPI CreateIoCompletionPort(...);
            ( 这个对象可以收发对象)
             HANDLE CreateIoCompletionPort (
        HANDLE FileHandle,              // 句柄,首次创建时填INVALID_HANDLE_VALUE
        HANDLE ExistingCompletionPort,  // I/O完成端口句柄 ,首次创建给NULL
        ULONG_PTR CompletionKey,        // 创建自定义对象
        DWORD NumberOfConcurrentThreads //允许应用程序同时执行的线程数量,填0,根据CPU核数自动计算核数
    
      该函数实际用于两个明显有别的目的:
      a. 用于创建一个完成端口对象。
      b. 将一个句柄同完成端口关联到一起。
   

  2、关联完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
         (关联需要通过IOCP收发数据的socket)
     
  3.向IOCP队列投递接受连接的请求:BOOL AcceptEx(...);
      通知IOCP,让IOCP建立连接(可以异步操作),
      它可以接收连接,还可以在建立连接之后,等客户端发第一次数据(或者在建立连接之后等客户端不收后边的数据)
    
      BOOL AcceptEx(   
         SOCKET sListenSocket,   //监听socket,之前用到的socket
         SOCKET sAcceptSocket, //用来接收传入socket,与客户端socket建立连接     
         PVOID lpOutputBuffer,  //用来接收数据的缓冲区
         DWORD dwReceiveDataLength,    //缓冲区大小,一般填0
         DWORD dwLocalAddressLength,   //本地地址sockaddr大小,
                此值必须至少比正在使用的传输协议的最大地址长度多16个字节
         DWORD dwRemoteAddressLength,  //远程地址信息保留的字节数,此值必须至少        
              比正在使用的传输协议的最大地址长度多16个字节(填写同上)
         LPDWORD lpdwBytesReceived,  //返回数据的大小
         LPOVERLAPPED lpOverlapped);
    
  4、.检测队列,从队列中取出完成的请求 BOOL WINAPI GetQueuedCompletionStatus(...);

   BOOL GetQueuedCompletionStatus(
     HANDLE CompletionPort,       // 句柄
     LPDWORD lpNumberOfBytes,     // 接收字节数
     PULONG_PTR lpCompletionKey,  // 自定义参数
     LPOVERLAPPED *lpOverlapped,  //返回的结构体参数
     DWORD dwMilliseconds);      //等待的时间
   

  5、投递一个队列完成状态:BOOL WINAPI PostQueuedCompletionStatus(...);

四.  使用实例:(使用IOCP用来处理收发数据)

 

服务端:

  1 #include <iostream>
  2 #include <vector>
  3 using namespace std;
  4 
  5 #define FD_SETSIZE 128
  6 #define WIN32_LEAN_AND_MEAN
  7 #include <windows.h>
  8 #include <Winsock2.h>
  9 #pragma comment(lib, "Ws2_32.lib")
 10 
 11 #include <Mswsock.h>
 12 #pragma comment(lib, "Mswsock.lib")
 13 
 14 void InitWs2();
 15 void UninitWs32();
 16 void PostAccept(SOCKET sockListen, HANDLE hIocp);
 17 void PostRecv(SOCKET sock);
 18 
 19 enum IO_EVENT
 20 {
 21     IO_ACCEPT,
 22     IO_RECV,
 23     IO_SEND
 24 };
 25 
 26 struct MYOV :public OVERLAPPED
 27 {
 28     MYOV(SOCKET sock, IO_EVENT event)
 29     {
 30         memset(this, 0, sizeof(MYOV));
 31         m_sockClient = sock;
 32         m_buf.buf = m_btBuf;
 33         m_buf.len = sizeof(m_btBuf);
 34         m_dwBytesRecved = 0;
 35         m_dwFlag = 0;
 36         m_event = event;
 37     }
 38     IO_EVENT m_event;
 39     SOCKET m_sockClient;
 40     WSABUF m_buf;
 41     CHAR m_btBuf[MAXBYTE];
 42     DWORD m_dwBytesRecved;
 43     DWORD m_dwFlag;
 44 };
 45 
 46 int main()
 47 {
 48     InitWs2();
 49 
 50     SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 51     if (sockServer == SOCKET_ERROR)
 52     {
 53         printf("socket 创建失败\r\n");
 54         return 0;
 55     }
 56     else
 57     {
 58         printf("socket 创建成功\r\n");
 59     }
 60 
 61     //2)
 62     sockaddr_in si;
 63     si.sin_family = AF_INET;
 64     si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
 65     si.sin_port = htons(9527);
 66     int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si));
 67     if (nRet == SOCKET_ERROR)
 68     {
 69         printf("绑定端口失败\r\n");
 70         return 0;
 71     }
 72     else
 73     {
 74         printf("绑定端口成功\r\n");
 75     }
 76 
 77     //3)
 78     nRet = listen(sockServer, SOMAXCONN);
 79     if (nRet == SOCKET_ERROR)
 80     {
 81         printf("监听失败 \r\n");
 82         return 0;
 83     }
 84     else
 85     {
 86         printf("监听成功 \r\n");
 87     }
 88 
 89     //1)创建IOCP对象
 90     ULONG uKey = 0;
 91     HANDLE hIocp = CreateIoCompletionPort(
 92         INVALID_HANDLE_VALUE,
 93         NULL,
 94         NULL,
 95         0);
 96     
 97     //2) 关联IOCP和socket对象
 98     HANDLE bRet=CreateIoCompletionPort(
 99         (HANDLE)sockServer, 
100         hIocp,
101         NULL, 
102         0);
103 
104     //3)投递一个接收连接的请求
105     PostAccept(sockServer,hIocp);
106 
107     //遍历队列
108     while (true)
109     {
110         DWORD dwBytesTranfered = 0;
111         ULONG_PTR uKey;
112         LPOVERLAPPED pOv = NULL;
113         GetQueuedCompletionStatus(
114             hIocp,
115             &dwBytesTranfered, 
116             &uKey,
117             &pOv,
118             INFINITE
119         );
120 
121 
122         MYOV* pov = (MYOV*)pOv; 
123 
124         switch (pov->m_event) 
125         {
126             //接收新的连接
127         case IO_ACCEPT:
128             //连接完成后,再次投递一个连接的请求
129             PostAccept(sockServer, hIocp);
130             cout << " 有新的连接接入" << endl;
131             PostRecv(pov->m_sockClient);
132             break;
133         case IO_RECV:
134             //投递一个接收数据的请求
135             printf("接收到数据%s\r\n", pov->m_btBuf);
136             PostRecv(pov->m_sockClient);
137             break;
138         default:
139             break;
140         }
141     }
142 }
143 
144 void PostRecv(SOCKET sock)
145 {
146     //接收数据的请求
147     MYOV* pOv = new MYOV(sock, IO_RECV);
148     int nRet = WSARecv(
149         sock,
150         &pOv->m_buf, 1,
151         &pOv->m_dwBytesRecved,
152         &pOv->m_dwFlag,
153         pOv,
154         NULL);
155 }
156 
157 void PostAccept(SOCKET sockListen,HANDLE hIocp)
158 {
159     //接收连接的请求
160     SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
161     HANDLE hRet = CreateIoCompletionPort(
162         (HANDLE)sockClient,
163         hIocp,
164         NULL,
165         0);
166 
167     char szBuff[MAXBYTE] = { 0 };
168     DWORD dwRecved = 0;
169 
170     MYOV* pOv = new MYOV(sockClient, IO_ACCEPT);
171 
172     AcceptEx(
173         sockListen,
174         sockClient,
175         szBuff,
176         0,
177         sizeof(sockaddr) + 16,
178         sizeof(sockaddr) + 16,
179         &dwRecved,
180         pOv
181     );
182 }
183 
184 void InitWs2()
185 {
186     WORD wVersionRequested;
187     WSADATA wsaData;
188     int err;
189 
190     wVersionRequested = MAKEWORD(2, 2);
191     err = WSAStartup(wVersionRequested, &wsaData);
192     if (err != 0) {        
193         return;
194     }
195 
196     if (LOBYTE(wsaData.wVersion) != 2 ||
197         HIBYTE(wsaData.wVersion) != 2) {
198         WSACleanup();
199         return;
200     }
201 }
202 
203 void UninitWs32()
204 {
205     WSACleanup();
206 }

客户端:

#include <iostream>
using namespace std;

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib, "Ws2_32.lib")

void InitWs2();
void UninitWs32();

int main()
{
    InitWs2();

    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockClient == SOCKET_ERROR)
    {
        printf("socket 创建失败\r\n");
        return 0;
    }
    else
    {
        printf("socket 创建成功\r\n");
    }

    sockaddr_in si;
    si.sin_family = AF_INET;
    si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    si.sin_port = htons(9527);
    int nRet = connect(sockClient, (sockaddr*)&si, sizeof(si));
    if (nRet == SOCKET_ERROR)
    {
        printf("连接服务器失败 \r\n");
        return 0;
    }
    else
    {
        printf("连接服务器成功 \r\n");
    }

    while (true)
    {
        char szBuff[MAXBYTE] = { 0 };
        std::cin >> szBuff;

        nRet = send(sockClient, szBuff, sizeof(szBuff), 0);
        if (nRet == SOCKET_ERROR)
        {
            printf("发送失败\r\n");
        }
        else
        {
            printf("发送成功 \r\n");
        }
    }
}

void InitWs2()
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        return;
    }

    if (LOBYTE(wsaData.wVersion) != 2 ||
        HIBYTE(wsaData.wVersion) != 2) {                        
        WSACleanup();
        return;
    }
}

void UninitWs32()
{
    WSACleanup();
}

测试效果:

 

posted @ 2021-04-23 10:43  Wings_shadow  阅读(3795)  评论(1编辑  收藏  举报