windows下的IOCP

对应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);
}

猜你喜欢

转载自blog.csdn.net/qq_36946274/article/details/80785491
今日推荐