• 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 创建失败
    ");
     54         return 0;
     55     }
     56     else
     57     {
     58         printf("socket 创建成功
    ");
     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("绑定端口失败
    ");
     70         return 0;
     71     }
     72     else
     73     {
     74         printf("绑定端口成功
    ");
     75     }
     76 
     77     //3)
     78     nRet = listen(sockServer, SOMAXCONN);
     79     if (nRet == SOCKET_ERROR)
     80     {
     81         printf("监听失败 
    ");
     82         return 0;
     83     }
     84     else
     85     {
     86         printf("监听成功 
    ");
     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
    ", 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 创建失败
    ");
            return 0;
        }
        else
        {
            printf("socket 创建成功
    ");
        }
    
        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("连接服务器失败 
    ");
            return 0;
        }
        else
        {
            printf("连接服务器成功 
    ");
        }
    
        while (true)
        {
            char szBuff[MAXBYTE] = { 0 };
            std::cin >> szBuff;
    
            nRet = send(sockClient, szBuff, sizeof(szBuff), 0);
            if (nRet == SOCKET_ERROR)
            {
                printf("发送失败
    ");
            }
            else
            {
                printf("发送成功 
    ");
            }
        }
    }
    
    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();
    }

    测试效果:

  • 相关阅读:
    经典的笔试题python操作数据库和python设计模式【多测师_王sir】
    上证所python笔试题【多测师_王sir】
    银行移动消费信贷业务梳理【多测师_王sir】
    查看log.txt 日志文件中包含关键字x123或者x124的行,以及该行前后10行内容,并输出到out.txt中【多测师_王sir】【Linux题目】
    文件权限设置
    windows OpenSSH WARNING: UNPROTECTED PRIVATE KEY FILE!
    vue对象合并
    elk安装配置
    ElasticSearch
    Elastic Search之Search API(Query DSL)、字段类查询、复合查询
  • 原文地址:https://www.cnblogs.com/zhaoyixiang/p/14692753.html
Copyright © 2020-2023  润新知