完成端口IOCP

一、概述

IOCP就是单独实现一个线程用来处理所有与客户端的IO的模型

不仅仅是只创建一个线程处理IO,而是至少创建一个线程来负责IO前后的全部处理。

理解IOCP重点不要集中于线程,而是观察:1.IO是否以非阻塞模式工作;2.如果确认非阻塞模式的IO是否完成。

IOCP的完成端口不是指TCP/IP端口号,而是类似一个消息队列,由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O完成之后,其对应的工作线程就会收到一个通知,然后进行其他的操作。IOCP是异步I/O,其依赖于一个工作者线程池。使用工作者线程池限制线程的数量以避免创建太多thread而导致在切换线程时浪费大量的时间。

二、主要函数

1.创建完成端口对象

2.建立完成端口对象与套接字直接的联系

IOCP中已完成的IO信息将被注册到完成端口对象。这个过程并非单纯的注册,首先经过请求过程:“该套接字的IO完成时,把状态信息注册到指定CP对象”。

该过程称为“”套接字和CP对象直接的连接请求“。

#include<windows.h>
// 成功返回CP对象句柄,失败返回NULL
HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
     ULONG_PTR CompletionKey,
    DWORD NumberOfConcurrentThreads);
// 创建CP对象时,
// 第一个参数为INVALID_HANDLE_VALUE
// 第二个参数为NULL
// 第三个参数为0
// 第四个参数为指定线程并发数,为0表明为系统支持的最优线程数
// 建立CP对象与套接字联系时
// 第一个参数为连接至CP对象的套接字句柄
// 第二个参数为创建的CP对象
// 第三个参数为传递已完成IO相关信息,在GetQueuedCompletionStatus函数中讨论
// 第四个参数无论传递何值,只要第二个参数为非NULL就会忽略。

绑定之后,只有套接字的IO完成,相关信息就会注册到CP对象中。

3.确认完成端口已完成的IO和线程的IO处理

#include<windows.h>
// 成功返回true, 失败返回false
BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytes,
    PULONG_PTR lpCompletionKey, LPOVERLAPPED *lpOverlapped, DWORD dwMilliseconds);
//CompletionPort 创建并绑定后的完成端口
//lpNumberOfBytes 保存IO过程中传输的数据大小的变量地址值
//lpCompletionKey 用于保存CreateIoCompletionPort绑定的第三个参数的地址值 
//lpOverlapped 用于保存WSARecv和WSASend时传递的Overlapped参数地址的变量地址值
//dwMilliseconds 用于保存超时信息,超过返回false并跳出函数,
    传入INFINITE,程序阻塞至已完成IO信息完成写入CP对象

注意:

CreateIoCompletionPort的第三个参数是结构体,lpCompletionKey保存的时候该指针的地址

lpOverlapped 也是同样的保存指针的地址

GetQueuedCompletionStatus由处理IOCP中已完成的IO的线程调用

如前所书,IOCP创建全职IO线程,由该线程针对所有客户端进行IO。而且reateIoCompletionPort函数中也有参数用于指定分配给CP对象的最大线程数:是否自动创建线程并处理IO?当然不是!

由程序员自行创建调用WSASend和WSARecv等IO线程,只是该线程为了确认IO的完成会调用GetQueuedCompletionStatus函数。

虽然任何线程都能调用GetQueuedCompletionStatus函数,但是实际得到IO完成信息的线程数不会超过CreateIoCompletionPort指定的最大线程数

三、例子

1.回声Server例子

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <process.h>
#include <windows.h>

#define BUF_SIZE 100
#define READ 3
#define WRITE 5

typedef struct
{
    SOCKET hClntSock;
    SOCKADDR clntAdr;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

typedef struct
{
    OVERLAPPED overlapped;
    WSABUF wsaBuf;
    char buffer[BUF_SIZE];
    int rwMode;
}PER_IO_DATA, *LPER_IO_DATA;

DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void ErrorHandling(char *message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    HANDLE hComPort;
    SYSTEM_INFO sysInfo;
    LPER_IO_DATA ioInfo;
    LPER_HANDLE_DATA handleInfo;

    SOCKET hServSock;
    SOCKADDR_IN servAdr;
    int recvBytes,i,flags=0,mode = 1;

    if(argc!=2)
    {
        printf("Usage: %s <prot> \n",argv[0]);
        exit(1);
    }

    if(WSAStartup(MAKEWORD(2,2), &wsaData))
        ErrorHandling("WSAStartup() error");
    
    hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0 , 0);
    GetSystemInfo(&sysInfo);
    for(i=0;i<sysInfo.dwNumberOfProcessors;i++)
    {
        _beginthreadex(NULL,0,EchoThreadMain,(LPVOID)hComport,0,NULL);
    }

    hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    ioclsocket(hServSock, FINOBIO, &mode);

    memset(&servAdr,0,sizeof(servAdr));
    lisnAdr.sin_family = AF_INET;
    lisnAdr.sin_port = htons(atoi(argv[1]));
    lisnAdr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(SOCKET_ERROR == bind(hServSock,(SOCKADDR*)&servAdr,sizeof(servAdr)))
        ErrorHandling("bind error");
    
    if(SOCKET_ERROR == listen(hServSock,5))
        ErrorHandling("listen error");
    
    while(1)
    {
        SOCKET hClntSock;
        SOCKADDR_IN clntAdr;

        hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, sizeof(clntAdr));
        handleInfo = (LPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
        handleInfo->hClntSock = hClntSock;
        memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);

        CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo,0);

        ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
        memset(&ioInfo->overlapped,0,sizeof(OVERLAPPED));
        ioInfo->wsaBuf.len=BUF_SIZE;
        ioInfo->wsaBuf.buf=ioInfo->buffer;
        // IOCP本身不会帮我区分输入完成还是输出完成状态,无论输入还是输出,只通知IO完成状态
        // 因此需要额外的变量来区分2中IO
        ioInfo->rwMode=Read;
        // 第7个参数overlapped的地址与PER_IO_DATA结构体ioInfo的地址相同,相当于传入了ioInfo结构体的地址
        WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf), 1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);
    }
    return 0;
}

包含overlapped的结构体可以包含其他参数,从而通过WSASend和WSARecv可以获得更多的参数信息。

通过其中的标志来决定是发送还是接收,完成端口本身是不会区分的。

而Completion Key是创建完成端口的时候设定的。

通过GetQueuedCompletionStatus的第三个参数和第四个参数,分别是在CreatIoCompletionPort和WSARecv或WSASend的时候绑定的,

2.例子

DWORD WINAPI EchoThreadMain(LPVOID pComPort)
{
    HANDLE hComport = (HANDLE)pComport;
    LPER_IO_DATA ioInfo;
    LPER_HANDLE_DATA handleInfo;
    DWORD transBytes
    SOCKET sock;

    GetQueuedCompletionStatus(hComport, &transBytes, (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInof, INFINITE);

    sock = handleInfo->hClntSock;

    if(READ == ioInfo->rwMode)// 表明接收完毕 WSARecv完毕
    {
        puts("message recieve");
        if(transBytes == 0)
        {
            closesocket(sock);
            free(handleInfo);
            free(ioInfo);
            continue;
        }

        memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
        ioInfo->wsaBuf.len = transBytes;
        ioInfo->rwMode = WRITE;//表明接下来的操作是要写,发送
        WSASend(hComport, &(ioInfo->wsaBuf), 1, NULL, 0, &(ioInfo->overlapped), NULL);

        ioInfo = (LPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
        memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
        ioInfo->wsaBuf.buf = ioInfo->Buffer;
        ioInfo->wsaBuf.len = BUF_SIZE;
        ioInof->rwMode = READ;// 表明接下来的状态是要读,接收

        WSARecv(hComport, &(ioInfo->wsaBuf), 1, NULL, 0, &(ioInfo->overlapped), NULL);
    }
}

四、分析

在硬件性能和带宽充足的情况下,响应时间和并发数量出问题,优先考虑两点:

  • 低效的IO结构或低效的CPU使用
  • 数据库设计和查询语句结构

IOCP性能更优的原因:

  • 非阻塞IO,不会因为IO引发延迟
  • 查找已经完成IO的时候不需要循环,select需要遍历套接字数组
  • 无需将作为IO对象的套接字句柄保存到数组中管理
  • 可以调整处理的线程,所以可以在实验数据的基础上选用合适的线程数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值