IOCP基础封装

前几篇文章介绍了集中linux epoll的封装方式,本篇回归windows,介绍一个IOCP的封装。

首先介绍要达到的目的:

1) 导出基本接口,作为一个更高层跨平台网络库的基础组件

2) 导出的接口用法与epoll版本大致相当,以方便日后的跨平台封装

3) 默认的使用方式是单线程的,与epoll一致。

下面是接口文件:

/*    
    Copyright (C) <2012>  <huangweilook@21cn.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/    
#ifndef _KENDYNET_H
#define _KENDYNET_H

//重叠结构
struct OverLapContext
{
    OVERLAPPED    m_overLapped;
    WSABUF*       wbuf;
    DWORD         buf_count;
    unsigned char m_Type;
};
struct Socket;

//socket的简单封装
typedef struct Socket
{
    SOCKET sock;
    HANDLE complete_port;
    void (*RecvFinish)(struct Socket*,struct OverLapContext*,DWORD);
    void (*SendFinish)(struct Socket*,struct OverLapContext*,DWORD);
}*Socket_t;

int    InitNetSystem();
void   CleanNetSystem();

HANDLE CreateNetEngine(DWORD NumberOfConcurrentThreads);
void   CloseNetEngine(HANDLE);
int    RunEngine(HANDLE,DWORD timeout);
int    Bind2Engine(HANDLE,Socket_t);

//now表示是否立即发起操作
int    WSA_Send(Socket_t,struct OverLapContext*,int now,DWORD *lastErrno);
int    WSA_Recv(Socket_t,struct OverLapContext*,int now,DWORD *lastErrno);



#endif

其中的Engine就是IOCP,使用方式就是RunEngine,在函数内部调用GetQueuedCompletionStatus以处理事件.事件处理完成后,

回调RecvFinish/SendFinish.在简单的单线程程序中,只需要创建一个engine,并在主线程调用RunEngine即可.当然,因为完成

队列其实是线程安全的,所以多个线程同时对一个IOCP调用engine也是可以的。但我不建议这么做,如果需要多线程,可以创建

N个engine和工作线程,各工作线程在自己的engine上执行RunEngine即可(与epoll版本一致).

下面贴出实现:

#include <winsock2.h>
#include <WinBase.h>
#include <Winerror.h>
#include "KendyNet.h"
#include "stdio.h"
enum
{
    IO_RECVREQUEST = 1<<1,   //应用层接收请求
    IO_SENDREQUEST = 1<<3,   //应用层发送请求
    IO_RECVFINISH =  1<<2,//接收完成
    IO_SENDFINISH =  1<<4,   //发送完成
};


enum
{
    IO_RECV = (1<<1) + (1<<2),
    IO_SEND = (1<<3) + (1<<4),
    IO_REQUEST = (1<<1) + (1<<3),
};

int    InitNetSystem()
{
    int nResult;
    WSADATA wsaData;
    nResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (NO_ERROR != nResult)
    {
        printf("\nError occurred while executing WSAStartup().");
        return -1; //error
    }
    return 0;
}
void   CleanNetSystem()
{
     WSACleanup();
}


HANDLE CreateNetEngine(DWORD NumberOfConcurrentThreads)
{
    HANDLE CompletePort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, NumberOfConcurrentThreads);
    if(NULL == CompletePort)
    {
        printf("\nError occurred while creating IOCP: %d.", WSAGetLastError());
    }
    return CompletePort;
}

void   CloseNetEngine(HANDLE CompletePort)
{
    CloseHandle(CompletePort);
}

static int raw_Send(Socket_t s,struct OverLapContext *overLapped,DWORD *lastErrno)
{
    DWORD dwFlags = 0;
    DWORD dwBytes = 0;
    int nBytesSend = WSASend(s->sock, overLapped->wbuf, overLapped->buf_count, 
        &dwBytes, dwFlags, (OVERLAPPED*)overLapped, NULL);

    if(SOCKET_ERROR == nBytesSend)
        *lastErrno = WSAGetLastError();
    return dwBytes;
}

static int raw_Recv(Socket_t s,struct OverLapContext *overLapped,DWORD *lastErrno)
{
    DWORD dwFlags = 0;
    DWORD dwBytes = 0;
    int nBytesSend = WSARecv(s->sock, overLapped->wbuf, overLapped->buf_count, 
        &dwBytes, &dwFlags, (OVERLAPPED*)overLapped, NULL);

    if(SOCKET_ERROR == nBytesSend)
        *lastErrno = WSAGetLastError();
    return dwBytes;
}


typedef void (*CallBack)(struct Socket*,struct OverLapContext*,DWORD);

int    RunEngine(HANDLE CompletePort,DWORD timeout)
{
    DWORD bytesTransfer;
    Socket_t       socket;
    struct OverLapContext *overLapped = 0;
    DWORD lastErrno = 0;
    BOOL bReturn;
    CallBack call_back;
    for( ; ; )
    {
        call_back = 0;
        bReturn = GetQueuedCompletionStatus(
            CompletePort,&bytesTransfer,
            (LPDWORD)&socket,
            (OVERLAPPED**)&overLapped,timeout);
        
        if(FALSE == bReturn || socket == NULL || overLapped == NULL)
        {
            break;
        }
        if(0 == bytesTransfer)
        {
            //连接中断
            if(overLapped->m_Type & IO_RECV)
                call_back = socket->RecvFinish;    
            else
                call_back = socket->SendFinish;
            bytesTransfer = 0;
        }
        else
        {
            if(overLapped->m_Type & IO_REQUEST)
            {
                overLapped->m_Type = overLapped->m_Type << 1;
                if(overLapped->m_Type  & IO_RECVFINISH)
                    bytesTransfer = raw_Recv(socket,overLapped,&lastErrno);
                else if(overLapped->m_Type  & IO_SENDFINISH)
                    bytesTransfer = raw_Send(socket,overLapped,&lastErrno);
                else
                {
                    //出错
                    continue;
                }

                if(bytesTransfer <= 0 && lastErrno != 0 && lastErrno != WSA_IO_PENDING)
                    bytesTransfer = 0;
                else
                    continue;
            }
        
            if(overLapped->m_Type & IO_RECVFINISH)
                call_back = socket->RecvFinish;
            else if(overLapped->m_Type & IO_SENDFINISH)
                call_back = socket->SendFinish;
            else
            {
                //出错
                continue;
            }
        }

        if(call_back)
            call_back(socket,overLapped,bytesTransfer);
    }
    return 0;
}

int    Bind2Engine(HANDLE CompletePort,Socket_t socket)
{
    HANDLE hTemp;
    if(!socket->RecvFinish || !socket->SendFinish)
        return -1;
    hTemp = CreateIoCompletionPort((HANDLE)socket->sock, CompletePort,(ULONG_PTR)socket, 0);
    if (NULL == hTemp)
        return -1;
    socket->complete_port = CompletePort;
    return 0;
}

int    WSA_Send(Socket_t socket,struct OverLapContext *OverLap,int now,DWORD *lastErrno)
{
    if(!socket->complete_port)
        return -1;
    ZeroMemory(&OverLap->m_overLapped, sizeof(OVERLAPPED));
    if(!now)
    {
        OverLap->m_Type = IO_SENDREQUEST;
        PostQueuedCompletionStatus(socket->complete_port,1,(ULONG_PTR)socket,(OVERLAPPED*)OverLap);
    }
    else
    {
        OverLap->m_Type = IO_SENDFINISH;
        return raw_Send(socket,overLapped,lastErrno);
    }
    return 0;
}

int    WSA_Recv(Socket_t socket,struct OverLapContext *OverLap,int now,DWORD *lastErrno)
{
    if(!socket->complete_port)
        return -1;
    ZeroMemory(&OverLap->m_overLapped, sizeof(OVERLAPPED));
    if(!now)
    {
        OverLap->m_Type = IO_RECVREQUEST;
        PostQueuedCompletionStatus(socket->complete_port,1,(ULONG_PTR)socket,(OVERLAPPED*)OverLap);
        return 0;
    }
    else
    {    
        OverLap->m_Type = IO_RECVFINISH;
        return raw_Recv(socket,overLapped,lastErrno);
    }
}

代码一点点,不做介绍了,下面是一个简单的echo测试

#include <stdio.h>
#include <winsock2.h>
#include <WinBase.h>
#include <Winerror.h>
#include "KendyNet.h"

struct connection
{
    struct Socket socket;
    WSABUF wsendbuf;
    WSABUF wrecvbuf;
    char   sendbuf[128];
    char   recvbuf[128];
    struct OverLapContext send_overlap;
    struct OverLapContext recv_overlap;
};

void RecvFinish(struct Socket *s,struct OverLapContext *overLap,DWORD bytestransfer)
{
    struct connection *c = (struct connection*)s;
    if(bytestransfer <= 0)
        free(c);
    else
    {
        DWORD lastErrno = 0;
        memcpy(c->sendbuf,c->recvbuf,bytestransfer);
        c->wsendbuf.len = bytestransfer;
        WSA_Send(s,&c->send_overlap,1,&lastErrno);
    }

}

void SendFinish(struct Socket *s,struct OverLapContext *overLap,DWORD bytestransfer)
{
    struct connection *c = (struct connection*)s;
    if(bytestransfer <= 0)
        free(c);
    else
    {
        DWORD lastErrno = 0;
        WSA_Recv((Socket_t)c,&c->recv_overlap,1,&lastErrno);
    }
}

struct connection* CreateConnection(SOCKET s)
{
    struct connection *c;
    c = malloc(sizeof(*c));
    ZeroMemory(c, sizeof(*c));
    c->socket.sock = s;
    c->socket.RecvFinish = &RecvFinish;
    c->socket.SendFinish = &SendFinish;
    c->wrecvbuf.buf = c->recvbuf;
    c->wrecvbuf.len = 128;
    c->recv_overlap.buf_count = 1;
    c->recv_overlap.wbuf = &c->wrecvbuf;

    c->wsendbuf.buf = c->sendbuf;
    c->wsendbuf.len = 0;
    c->send_overlap.buf_count = 1;
    c->send_overlap.wbuf = &c->wsendbuf;
    return c;
}


DWORD WINAPI Listen(void *arg)
{

    HANDLE *iocp = (HANDLE*)arg;
    HANDLE complete_port = *iocp;

    struct sockaddr_in    addr;
    int                         optval=1;                        //Socket属性值
    unsigned long               ul=1;
    struct linger lng;
    struct sockaddr_in ClientAddress;
    int nClientLength = sizeof(ClientAddress);
    struct connection *c;
    
    
    SOCKET ListenSocket;
    ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    
    if (INVALID_SOCKET == ListenSocket) 
    {
        printf("\nError occurred while opening socket: %d.", WSAGetLastError());
        return 0;
    }
    
    addr.sin_family        = AF_INET;
    addr.sin_port        = htons(8010);
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");


    setsockopt(ListenSocket,SOL_SOCKET,SO_EXCLUSIVEADDRUSE,(char*)&optval,sizeof(optval));  //禁止端口多用
    setsockopt(ListenSocket,IPPROTO_TCP,TCP_NODELAY,(char*)&optval,sizeof(optval));         //不采用延时算法 
    setsockopt(ListenSocket,SOL_SOCKET,SO_DONTLINGER,(char*)&optval,sizeof(optval));        //执行立即关闭
    
    lng.l_linger=0;
    lng.l_onoff=1;
    setsockopt(ListenSocket,SOL_SOCKET,SO_LINGER,(char*)&lng,sizeof(lng));
    //禁止重用本地端口
    if ((bind(ListenSocket, (struct sockaddr *)&addr, sizeof( struct sockaddr_in))) == SOCKET_ERROR)
    {
        closesocket(ListenSocket);
        return 0;
    }

    if((listen(ListenSocket, 5)) == SOCKET_ERROR)
    {
        closesocket(ListenSocket);
        return 0;
    }

    printf("listener 启动\n");

    while(1)
    {
        SOCKET client = accept(ListenSocket, (struct sockaddr*)&ClientAddress, &nClientLength);
        if (INVALID_SOCKET == client)
        {
            continue;
        }
        if(ioctlsocket(client,FIONBIO,(unsigned long*)&ul)==SOCKET_ERROR)
        {
            closesocket(client);
            continue;
        }

        c = CreateConnection(client);
        Bind2Engine(complete_port,(Socket_t)c);
        //发出第一个读请求
        WSA_Recv((Socket_t)c,&c->recv_overlap,0,0);

    }

    return 0;
}

int main()
{
    HANDLE iocp;
    DWORD dwThread;
    InitNetSystem();
    iocp = CreateNetEngine(0);
    
    
    CreateThread(NULL,0,Listen,&iocp,0,&dwThread);
    
    while(1)
        RunEngine(iocp,10);
    return 0;
}
原文地址:https://www.cnblogs.com/sniperHW/p/2482870.html