【网络】IO多路复用之IOCP

目录

一、工作原理

二、关键组件

三、使用步骤

四、IOCP示例

五、优点

六、缺点

七、应用场景


IOCP(Input/Output Completion Ports,输入输出完成端口)是Windows操作系统中一种高效的网络和文件I/O处理机制。它允许应用程序以异步方式处理多个I/O操作,同时能够减少线程的使用和提高系统的可扩展性。

一、工作原理

IOCP的核心思想是将I/O操作与具体的线程解耦,使用一个或多个完成端口来管理I/O操作的完成通知。当I/O操作(如socket的读写、文件的读写等)完成时,操作系统会将完成通知发送到与socket或文件句柄关联的完成端口。然后,一个或多个工作线程(Worker Threads)会从完成端口中取出这些完成通知,并对其进行处理。

二、关键组件

  1. 完成端口(Completion Port):一个系统级的队列,用于存储I/O操作的完成通知。
  2. 工作线程(Worker Threads):负责从完成端口中取出完成通知,并处理相应的I/O操作。
  3. I/O操作:如socket的accept、read、write等,以及与文件相关的读写操作。
  4. 关联:通过特定的API(如CreateIoCompletionPort)将句柄(如socket或文件句柄)与完成端口关联起来。

三、使用步骤

  1. 创建完成端口:使用CreateIoCompletionPort函数创建一个新的完成端口,或者将一个已存在的句柄(如无效句柄INVALID_HANDLE_VALUE)与完成端口关联。
  2. 关联句柄:使用CreateIoCompletionPort函数将需要异步处理的句柄(如socket句柄或文件句柄)与完成端口关联。
  3. 启动工作线程:创建并启动一个或多个工作线程,这些线程将循环调用GetQueuedCompletionStatus函数从完成端口中取出完成通知。
  4. 执行I/O操作:通过相应的API(如WSARecvWSASend等)执行I/O操作,并指定完成端口作为操作的完成通知目标。
  5. 处理完成通知:在工作线程的GetQueuedCompletionStatus调用中,当I/O操作完成时,该函数会返回,并带有完成通知的相关信息(如完成状态、字节传输数等)。工作线程根据这些信息处理I/O操作的结果。

四、IOCP示例

tcpserver.h

#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <Mswsock.h>

#define MAXNUM   64
#define ONEPAGE 4096

#define NT_ACCEPT  0
#define NT_READ    1
struct MySocket{
    OVERLAPPED  m_olp;//事件
    SOCKET   m_socket; //接收链接的socket
    char     m_szBuffer[ONEPAGE];  //缓冲区
    char     m_nType; //网络事件的类型

};
// //sock--包大小,缓冲区,偏移量
//struct stru_pack{
//    int   m_nPackSize;
//    char *m_pszbuf;
//    int   m_noffset;
//};
class TCPServer
{
public:
    TCPServer();
    ~TCPServer();
public:
    bool initNetWork(const char* szip ="127.0.0.1",short nport = 1234);
    void unInitNetWork(const char *szerr = "");
    bool sendData(SOCKET sock,const char* szbuf,int nlen);
    bool postAccept();
    void postRecv(MySocket *);
public:

    static DWORD WINAPI ThreadProc(LPVOID lpvoid);
private:
    SOCKET m_socklisten;
    std::list<HANDLE> m_lstThread;
    std::list<MySocket*> m_lstSocket;
    bool  m_bFlagQuit;
    HANDLE m_hIOCP;
public:


};

#endif // TCPSERVER_H

tcpserver.cpp

#include "tcpserver.h"

TCPServer::TCPServer()
{
    m_socklisten = 0;
    m_bFlagQuit = true;


}

TCPServer::~TCPServer()
{

}

bool TCPServer::initNetWork(const char *szip, short nport)
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        /* Tell the user that we could not find a usable */
        /* Winsock DLL.                                  */
        printf("WSAStartup failed with error: %d\n", err);
        return false;
    }

    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        unInitNetWork("Could not find a usable version of Winsock.dll\n");
        return false;
    }
    else
        printf("The Winsock 2.2 dll was found okay\n");
    m_socklisten =  socket(AF_INET,SOCK_STREAM,0);
    if(INVALID_SOCKET == m_socklisten){
        unInitNetWork("socket err\n");
        return false;
    }
    sockaddr_in addrserver;
    addrserver.sin_family = AF_INET;
    addrserver.sin_port = htons(nport);
    addrserver.sin_addr.s_addr = 0;
    if(SOCKET_ERROR == bind(m_socklisten,(const struct sockaddr*)&addrserver,sizeof(addrserver))){
        unInitNetWork("bind err\n");
        return false;
    }
    if(SOCKET_ERROR == listen(m_socklisten,10)){
       unInitNetWork("listen err\n");
        return false;
    }
    //1.创建IOCP
    m_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
    //2.将listen交给IOCP管理
    CreateIoCompletionPort((HANDLE)m_socklisten,m_hIOCP,m_socklisten,0);
    //3.创建空闲的socket
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    for(unsigned int i = 0;i < si.dwNumberOfProcessors*2;i++){
        postAccept();
    }
    //4.创建线程池
//   //创建线程
     for(unsigned int i = 0;i < si.dwNumberOfProcessors*2;i++){
        HANDLE hThread = CreateThread(0,0,&ThreadProc,this,0,0);
        if(hThread)
            m_lstThread.push_back(hThread);
     }
    return true;
}

bool TCPServer::postAccept()
{
    MySocket *p= new MySocket;
    p->m_socket = socket(AF_INET,SOCK_STREAM,0);
    p->m_nType =NT_ACCEPT;
    p->m_olp.hEvent = WSACreateEvent();
    DWORD dwRecvNum;
    if(!AcceptEx(m_socklisten,
             p->m_socket,
             p->m_szBuffer,
             0,
             sizeof(sockaddr_in)+16,
             sizeof(sockaddr_in)+16,
             &dwRecvNum,
             &p->m_olp
                  )){
        if(WSAGetLastError() !=ERROR_IO_PENDING){
            closesocket(p->m_socket);
            WSACloseEvent(p->m_olp.hEvent);
            delete p;
            return false;

        }
      }

    m_lstSocket.push_back(p);
      return true;
}



DWORD TCPServer::ThreadProc(LPVOID lpvoid)
{
    TCPServer *pthis = (TCPServer*)lpvoid;
    DWORD dwNumberOfBytesTransferred;
    SOCKET sock;
    MySocket* psockex;
    DWORD dwFlag;
      while(pthis->m_bFlagQuit){
        //观察IOCP的状态
     dwFlag = GetQueuedCompletionStatus(pthis->m_hIOCP,
                                  &dwNumberOfBytesTransferred,
                                  (PULONG_PTR )&sock,
                                  (LPOVERLAPPED *)&psockex,
                                   INFINITE
                                  );
        if(!dwFlag){
            //如果客户端下线,需要将socket从链表中删掉
            auto ite = pthis->m_lstSocket.begin();
            while(ite !=pthis->m_lstSocket.end()){
                if((*ite) == psockex){
                    closesocket(psockex->m_socket);
                    WSACloseEvent(psockex->m_olp.hEvent);
                    delete psockex;
                    pthis->m_lstSocket.erase(ite);
                    break;
                }
                ite++;
            }
            continue;
     }
        if(!sock ||
         !psockex )
            continue;

        switch (psockex->m_nType) {
        case NT_ACCEPT:
        {
            //创建空闲额socktet
            pthis->postAccept();
            //主动投递接收数据请求
            pthis->postRecv(psockex);
            //将socket 交给IOCP管理
            CreateIoCompletionPort((HANDLE)psockex->m_socket,pthis->m_hIOCP,psockex->m_socket,0);
        }
            break;
        case NT_READ:
            printf("%s\n",psockex->m_szBuffer);
            pthis->postRecv(psockex);
            break;
        default:
            break;
        }

    }
    return 0;
}
void TCPServer::postRecv(MySocket *psockex)
{
    psockex->m_nType = NT_READ;
    WSABUF wb;
    wb.buf = psockex->m_szBuffer;
    wb.len = sizeof(psockex->m_szBuffer);
    DWORD dwNumberOfBytesRecvd;
    DWORD dwFlag = 0;
    if(WSARecv(psockex->m_socket,
            &wb,
            1,
            &dwNumberOfBytesRecvd,
            &dwFlag,
            &psockex->m_olp,
                0)){
        if(WSAGetLastError() != WSA_IO_PENDING ){

        }
    }
}

void TCPServer::unInitNetWork(const char *szerr )
{
    m_bFlagQuit = false;
    int nThreadNum =m_lstThread.size();
    while(nThreadNum-- >0){
        PostQueuedCompletionStatus(m_hIOCP,0,0,0);
    }
    for (auto& ite:m_lstThread) {

        if(WAIT_TIMEOUT == WaitForSingleObject(ite,100))
            TerminateThread(ite,-1);

        if(ite){
            CloseHandle(ite);
            ite = NULL;
        }
    }
    m_lstThread.clear();
    printf(szerr);
    if(m_socklisten){
       closesocket(m_socklisten);
        m_socklisten = 0;
    }
    auto ite = m_lstSocket.begin();
    while(ite !=m_lstSocket.end()){

           closesocket((*ite)->m_socket);
            WSACloseEvent((*ite)->m_olp.hEvent);
            delete (*ite);
        ite++;
    }
    m_lstSocket.clear();
    WSACleanup();
}

bool TCPServer::sendData(SOCKET sock, const char *szbuf, int nlen)
{
    if(!szbuf || nlen <=0)
        return false;
   //粘包
    //发送包大小
    if(send(sock,(char*)&nlen,sizeof(int),0)<=0)
        return false;
    //发送包内容
    if(send(sock,szbuf,nlen,0)<=0)
        return false;
    return true;
}





main.cpp

#include <QCoreApplication>
#include "network/tcpserver.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    TCPServer ts;
    if(ts.initNetWork())
        printf("server is running......\n");
    else
        printf("server err\n");
    return a.exec();
}

五、优点

  1. 高效性:IOCP能够显著减少线程的使用,因为多个I/O操作可以共享少量的工作线程。这减少了线程上下文切换的开销,并提高了系统的可扩展性。
  2. 灵活性:IOCP允许应用程序根据需要动态地调整工作线程的数量,以适应不同的负载情况。
  3. 易用性:虽然IOCP的实现相对复杂,但一旦设置好,它可以为应用程序提供强大且灵活的异步I/O处理能力。

六、缺点

  1. 复杂性:IOCP的实现相对复杂,需要深入理解Windows的I/O模型和线程同步机制。
  2. 调试难度:由于IOCP的异步特性和多线程环境,调试和排查问题可能会比较困难。

七、应用场景

IOCP适用于需要处理大量并发I/O操作的应用程序,如高性能的Web服务器、数据库服务器、文件服务器等。它可以帮助这些应用程序提高吞吐量、降低延迟,并更好地利用系统资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编程的小猴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值