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队列处理,不会与操作系统底层交互。

C++Socket编程—socket网络模型之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 创建失败rn");
 54         return 0;
 55     }
 56     else
 57     {
 58         printf("socket 创建成功rn");
 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("绑定端口失败rn");
 70         return 0;
 71     }
 72     else
 73     {
 74         printf("绑定端口成功rn");
 75     }
 76 
 77     //3)
 78     nRet = listen(sockServer, SOMAXCONN);
 79     if (nRet == SOCKET_ERROR)
 80     {
 81         printf("监听失败 rn");
 82         return 0;
 83     }
 84     else
 85     {
 86         printf("监听成功 rn");
 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("接收到数据%srn", 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 创建失败rn");
        return 0;
    }
    else
    {
        printf("socket 创建成功rn");
    }

    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("连接服务器失败 rn");
        return 0;
    }
    else
    {
        printf("连接服务器成功 rn");
    }

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

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

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();
}

测试效果:

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

原文链接: https://www.cnblogs.com/zhaoyixiang/p/14692753.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/209797

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 上午12:03
下一篇 2023年2月13日 上午12:03

相关推荐