zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    【模拟练习】[一]
    【搜索练习】【二】
    【搜索练习】【一】
    模板整理 (施工中 2017.8.30更新)
    常用STL整理 (施工中 2017.8.11更新)
    剑指Offer 反转链表
    剑指Offer 链表中倒数第k个结点
    剑指Offer 斐波那契数列
    剑指Offer 用两个栈实现队列
    剑指Offer 从尾到头打印链表
  • 原文地址:https://www.cnblogs.com/sniperHW/p/2482870.html
Copyright © 2011-2022 走看看