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

測試效果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM