欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

windows下的IOCP

程序员文章站 2024-02-24 11:45:58
...

对应Linux下的epoll,windows下也有实现IO复用的方法,那就是IOCP,又叫重叠IO,其实和异步IO没什么区别,《TCPIP网络编程》这本书也没有对重叠IP和异步IO做明确的区分。
首先,创建重叠IO的套接字。

SOKET WSAocket(int af,int type,int protocol,LPWSAPROTOCOL_INFO lpProtocolInfo,GROUP g,DWORD dwFlags);

成功时返回套接字句柄,失败时返回INVALID_SOKET。
af 协议族信息
前三个参数和普通套接字一致,后三个分别为
lpProtocolInfo 包含创建的套接字信息的WSAPROTOCOL_INFO结构体变量地址值,不需要时传递NULL。
g 为扩展函数而预约的参数,可以使用0。
dwFlags 套接字属性信息。

执行重叠IO的WSASend函数

int WSASend(SOKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesSent,DWORD dwFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);

1-7参数含义分别为:
1:套接字句柄
2:WSABUF结构体变量数组的地址值,其中存有待传输数据。
3:第二个参数中的数组长度
4:用于保存实际发送字节数的变量地址值
5:用于更改数据传输特性
6:WSAOVERLAPPED结构体变量的地址值,使用事件对象,用于确认完成数据传输。
7.传入Completion Routine函数的入口地址值,可以通过该函数确认是否完成数据传输。
接下来介绍第二个结构体参数类型
typedef struct __WSABUF
{
u_long len;
char FAR*buf;
}
可见其中存有缓冲数据的内容和长度。
第六个参数结构体如下:
typedef struct _WSAOVERLAPPE
{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED,*LPWSAOVERLAPPED;
大家想,WSASEND函数中参数lpNumberOfBytesSent可以获得实际传输数据的大小,既然WSASEND能实现异步调用,那么它就要在调用后立即返回,这时候可能还没传输完呢,那么它是如何得到传输数据的大小的呢?因此,在介绍WSARecv之前,我先介绍一个小函数:

      BOOL WSAGetOverlappedResult(

                      SOCKET s,                                                // SOCKET,不用说了

                      LPWSAOVERLAPPED lpOverlapped,  // 这里是我们想要查询结果的那个重叠结构的指针

                      LPDWORD lpcbTransfer,                       // 本次重叠操作的实际接收(或发送)的字节数

                      BOOL fWait,                // 设置为TRUE,除非重叠操作完成,否则函数不会返回

                                                           // 设置FALSE,而且操作仍处于挂起状态,那么函数就会返回FALSE

                                                           // 错误为WSA_IO_INCOMPLETE

                                                           // 不过因为我们是等待事件传信来通知我们操作完成,所以我们这里设

                                                          // 置成什么都没有作用

                      LPDWORD lpdwFlags       // 指向DWORD的指针,负责接收结果标志

                    );

进行重叠IO的WSARecv函数

int WSARecv(SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesRecvd,LPDWORD lpFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);

参数含义与WSASend基本相同,不再赘述。

综合运用实例

#include "stdafx.h"
#include <winsock2.h>
#include <stdio.h>
#include <iostream>

using namespace::std;
#define BUF_SIZE 1024

void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void CALLBACK WriteCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void ErrorHanding(char * message);

typedef struct
{
    SOCKET hClntSock;   //套接字句柄
    char buf[BUF_SIZE]; //缓冲
    WSABUF wsaBuf;      //缓冲相关信息
}PER_IO_DATA,*LPPER_IO_DATA;


void main()
{
    WSADATA wsaData;
    SOCKET hLisnSock, hRecvSock;
    SOCKADDR_IN lisnAdr, recvAdr;
    LPWSAOVERLAPPED lpOvlap;
    DWORD recvBytes;
    LPPER_IO_DATA hbInfo;
    int recvAdrSz;
    DWORD flagInfo = 0;
    u_long mode = 1;

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)//加载库并获取库信息填至wsaData
        ErrorHanding("socket start error!");

    //参数:协议族,套接字传输方式,使用的协议,WSA_PROTOCOL_INFO结构体地址/不需要时传null,扩展保留参数,套接字属性信息
    hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    //将hLisnSock句柄的套接字I/O模式(FIONBIO)改为mode中指定的形式:非阻塞模式
    ioctlsocket(hLisnSock, FIONBIO, &mode);

    //设置目标地址端口
    memset(&lisnAdr, 0, sizeof(lisnAdr));
    lisnAdr.sin_family = AF_INET;
    lisnAdr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    lisnAdr.sin_port = htons(6000);

    //套接字绑定
    if (bind(hLisnSock, (SOCKADDR*)&lisnAdr, sizeof(lisnAdr)) == SOCKET_ERROR)
        ErrorHanding("socket bind error!");

    //设置为监听模式
    if (listen(hLisnSock, 5) == SOCKET_ERROR)
        ErrorHanding("socket listen error!");

    recvAdrSz = sizeof(recvAdr);
    while (1)
    {
        //进入短暂alertable wait 模式,运行ReadCompRoutine、WriteCompRoutine函数
        SleepEx(100, TRUE);

        //非阻塞套接字,需要处理INVALID_SOCKET
        //返回的新的套接字也是非阻塞的
        hRecvSock = accept(hLisnSock, (SOCKADDR*)&recvAdr, &recvAdrSz);
        if (hRecvSock == INVALID_SOCKET)
        {
            //无客户端连接时,accept返回INVALID_SOCKET,WSAGetLastError()返回WSAEWOULDBLOCK
            if (WSAGetLastError() == WSAEWOULDBLOCK)
                continue;
            else
                ErrorHanding("accept() error");
        }

        puts("Client connected");

        //申请重叠I/O需要使用的结构体变量的内存空间并初始化
        //在循环内部申请:每个客户端需要独立的WSAOVERLAPPED结构体变量
        lpOvlap = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
        memset(lpOvlap, 0, sizeof(WSAOVERLAPPED));

        hbInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
        hbInfo->hClntSock = (DWORD)hRecvSock;

        (hbInfo->wsaBuf).buf = hbInfo->buf;
        (hbInfo->wsaBuf).len = BUF_SIZE;

        //基于CR的重叠I/O不需要事件对象,故可以用来传递其他信息
        lpOvlap->hEvent = (HANDLE)hbInfo;
        //接收第一条信息
        WSARecv(hRecvSock, &(hbInfo->wsaBuf), 1, &recvBytes, &flagInfo, lpOvlap, ReadCompRoutine);
    }
    closesocket(hRecvSock);
    closesocket(hLisnSock);
    WSACleanup();
    return;
}
//参数:错误信息,实际收发字节数,OVERLAPPED类型对象,调用I/O函数时传入的特性信息
void CALLBACK ReadCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
    //从lpoverlapped中恢复传递的信息
    LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
    SOCKET hSock = hbInfo->hClntSock;
    LPWSABUF bufInfo = &(hbInfo->wsaBuf);
    DWORD sentBytes;

    //接收到EOF,断开连接
    if (szRecvBytes == 0)
    {
        closesocket(hSock);
        free(hbInfo);
        free(lpOverlapped);
        puts("Client disconnected");
    }
    else
    {
        bufInfo->len = szRecvBytes;
        //将接收到的信息回传回去,传递完毕执行WriteCompRoutine(): 接收信息
        WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine);
    }
}

//参数:错误信息,实际收发字节数,OVERLAPPED类型对象,调用I/O函数时传入的特性信息
void CALLBACK WriteCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
    //从lpoverlapped中恢复传递的信息
    LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
    SOCKET hSock = hbInfo->hClntSock;
    LPWSABUF bufInfo = &(hbInfo->wsaBuf);
    DWORD recvBytes;
    DWORD flagInfo = 0;
    //接收数据,接收完毕执行ReadCompRoutine:发送数据
    WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine);
}

void ErrorHanding(char * message)
{
    cout << message << endl;
    exit(1);
}
相关标签: TCPIP