• 【转】IOCP创建


    转自:http://www.cmnsoft.com/wordpress/?p=248 感谢原作者。我在此整理一下:

    完成端口(IOCP)是WINDOWS平台上特有的一种技术。要使用IOCP技术,就要用到微软的WSA(windows socket api)。

    进行网络编程的套接口(socket)有UNIX套接口、伯克利套接口、WSA。其中使用最多的是伯克利套接口,因为他可在UNIX、WINDOWS、OS/2等计算机上使用。WSA套接口比伯克利套接口多了WSA三个字母。

    伯克利套接口:socket()、recv()、send()等。

    WSA套接口:WSASocket()、WSARecv()、WSASend()等。

    WSA2.0还增加了很多新的功能函数。IOCP要在WSA2.0以上才能实现,所以我们要检查我们的系统是否支持WSA2.0(现在大部份都支持)。

    IOCP的过程是,建立一个线程池(通常是有几个处理核心就创建几个线程。然后等待I/O操作提出,每一个链接进来的SOCKET分配一个线程去处理,处理完释放线程)。因为他是等待系统I/O操作的提出,而不是主动去轮询端口,所以他比传统方法更有效率。另外I/O的速度比CPU慢,而在IOCP里,数据是否传送到了由I/O提出,CPU只管处理数据问题,而不用轮询I/O端口,所以CPU的使用更高效。

    还有一点,IOCP一般用TCP链接服务器,当然他也可以做UDP。为什么用IOCP做UDP少呢。我觉得理由很简单,因为UDP比较适合局域网内使用,IOCP是处理大规模链接用的,比如上千人的连网游戏,而且需要同时管理几百、上千个套接字时才使用。而在局域网里很少出现这种情况。所以,我觉得ICOP还是适合TCP链接。

    IOCP经常用到的函数:

    //创建一个完成端口

    HANDLE CreateIoCompletionProt(
    HANDLE FileHandle, //socket接口
    HANDLE ExistingCompletionPort, //指定一个现有的IOCP
    DWORD CompletionKey, //指定要与某个特定套接字关联在一起的数据。
    DWORD NumberOfConcurrentThreads //一个IOCP上能同时执行几个线程,通常是0,
    让系统根据核心数情况来定。
    )
     
    //获取完成端口队列的状态。
    BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort, //一个等待的完成端口。
    LPDWORD lpNumberOfBytesTransferred, // I/O操作后实际接收或发送的字节数。
    LPDWORD lpCompletionkey, //CreateioCompletionProt中的completionkey。
    LPOVERLAPPED* lpOVerlapped, //接收完成的I/o操作的重叠结果。
    DWORD dwMilliseconds //调用者喜欢等待一个数据包在IOCP上出现的时间
    //设为INFINTE,则无休止等待。
    )
     
    //向完成端口线程发出指示“立即结束并退出”
    BOOL PostQueuedCompletionStatus(
    HANDLE CompletionPort, //想退出的完成端口
    DWORD dwNumbetOfBytesTransferred, 
    DWORD dwCompletionKey,
    LPOVERLAPPED lpOverlapped
    )
    后面三个参数的解释跟GetQueuedCompletionStatus是一样的。
     
    //接收活动SOCKET
    SOCKET WSAAPI WSAAccept (
    SOCKET s, //监听一个套接口。
    struct sockaddr FAR * addr, //通讯地址族
    int FAR * addrlen, //通讯地址族长度 
    LPCONDITIONPROC lpfnCondition, //用户提供的条件函数的进程实例地址。
    //该函数根据参数传入的调用者信息作出接受或拒绝的决定
    DWORD dwCallbackData //作为条件函数参数返回给应用程序的回调数据。
    )
     
    //向套接字发送数据
    int WSAAPI WSASend ( 
    SOCKET s, //发送目的套接字
    LPWSABUF lpBuffers, //发送数据缓冲区,这是个指针数组。存多组缓冲区
    DWORD dwBufferCount, // lpBuffers包含缓冲区数
    LPDWORD lpNumberOfBytesSent, //已发送字节数
    int iFlags, //标志位 
    LPWSAOVERLAPPED lpOverlapped, //WSAOVERLAPPED结构的指针 
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    //发送操作完成后调用的完成例程的指针
    )
    //返回已发送字节数
     
    //向套接字接送数据
    int WSARecv( 
    SOCKET s, //接收数据的套接字 
    LPWSABUF lpBuffers, // 接收缓冲区 
    DWORD dwBufferCount, // lpBuffers中WSABUF结构的数量 
    LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,
    //这里会返回函数调用所接收到的字节数 
    LPDWORD lpFlags, // 通常设置为0 
    LPWSAOVERLAPPED lpOverlapped, // “绑定”的重叠结构 
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 
    // 完成例程中将会用到的参数,我们这里设置为 NULL 
    )
    //返回WSA_IO_PENDING表示成功
    

      

    IOCP服务端的设计大概按下面几个步骤来完成。

    第一步:初始化WSA2.0并创建一个套接字。

    第二步:创建一个完成端口。

    第三步:根据系统CPU个数创建完成处理线程。

    第四步:监听套接字申请接入,并将申请进来的套接字加入完成端口队列。(由那个线程负责执行是完成端口自已完成的,不用管。

    而处理线程要做的事只有两件:

    第1件:端口是否有数据传输产生。
    第2件:如果有检查是发送还是接收,并做相应处理。

    为了方便主进程跟线程间传递数据,我们通常会建立一个数据结构来传递数据。
    比如我们建立一个IOCP_STR的结构体。

    struct IOCP_STR
    
    {
    OVERLAPPED overlapped; //这个是必须的。把结构构造为OVERLAPPED指针。
    WSABUF DataBuf; //数据指针,指向下面的buf
    char buf[BUF_SIZE]; //数据区大小
    DWORD sendbytes; //要发送多少个字节
    DWORD recvbytes; //要接收多少个字节
    }
    

      我们写一个IOCP服务器,他的作用是把客户端的发送过来的信息回送过去。这个程序分为四个部份

    第一部分:定义传输数据的结构体。

    #define BUF_SIZE 2048
    //传输数据信息
    struct IOCP_DATA
    {
        OVERLAPPED overlapped;
        WSABUF dataBuf;
        char buf[BUF_SIZE];
        DWORD recvbytes;
        DWORD sendbytes;
    };
    //socket信息
    struct IOCP_SOCKET
    {
        SOCKET m_socket;
    };
    

      第二部分:服务线程处理

    //服务器处理线程
    DWORD WINAPI ServerThread(LPVOID CompletionPort)
    {
        IOCP_SOCKET* m_Socket = NULL;
        IOCP_DATA* m_IocpData = NULL;
        DWORD m_BytesTransferred=0;
        DWORD Flags = 0;
        DWORD m_TransBytes = 0;
        HANDLE m_CompletionPort = CompletionPort;
        while(1)
        {
            if(GetQueuedCompletionStatus(m_CompletionPort,&m_BytesTransferred,
                    (LPDWORD)&m_Socket,(LPOVERLAPPED*)&m_IocpData,INFINITE)==0)
            {
                printf("getQueued error!
    ");
            }
     
            if(m_BytesTransferred == 0)
            {
                continue;
            }
     
            if(m_IocpData->recvbytes == 0)
            {
                //准备接收数据
                m_IocpData->recvbytes = m_BytesTransferred;
                m_IocpData->sendbytes = 0;
            }
            else
            {
                //准备发送数据
                m_IocpData->sendbytes += m_BytesTransferred;
            }
     
            //如果有数据没有发送,则发送出去
            if(m_IocpData->recvbytes > m_IocpData->sendbytes)
            {
                printf("send bytes...
    ");
                ZeroMemory(&(m_IocpData->overlapped),sizeof(OVERLAPPED));
                //计算要发送字节数和起始地址
                m_IocpData->dataBuf.buf = m_IocpData->buf + m_IocpData->sendbytes;
                m_IocpData->dataBuf.len = m_IocpData->recvbytes - m_IocpData->sendbytes;
                if(WSASend(m_Socket->m_socket,&(m_IocpData->dataBuf),1,&m_TransBytes,0,
                            &(m_IocpData->overlapped),NULL)==SOCKET_ERROR)
                {
                    printf("send bytes error!
    ");
                    return 0;
                }
            }
            else
            {
                //如果数据已经发送完,则等待接收新据
                printf("recv bytes...
    ");
                m_IocpData->recvbytes = 0;
                ZeroMemory(&(m_IocpData->overlapped),sizeof(OVERLAPPED));
                m_IocpData->dataBuf.len = BUF_SIZE;
                m_IocpData->dataBuf.buf = m_IocpData->buf;
                if(WSARecv(m_Socket->m_socket,&(m_IocpData->dataBuf),1,&m_TransBytes,
                &Flags,&(m_IocpData->overlapped),NULL)==SOCKET_ERROR)
                {
                    if(WSAGetLastError()!= ERROR_IO_PENDING)
                    printf("thread recv error
    ");
                }
            }
        }
        return 0;
    }
    

      第三部分:IOCP类

    class NIOCP
    {
    public:
        SOCKET m_ServerSocket;
        HANDLE m_CompletionPort;
        int num_Cores;
    private:
    // 检查是否支持2.0
        bool TestVersion()
        {
            WSADATA wsaData;
            WORD version = MAKEWORD(2,0);
            //初始化WinSock DLL库
            int ret = WSAStartup(version,&wsaData);
            if(ret!=0)
                return false;
            return true;
        }
     
    public:
        void CleanClient()
        {
            closesocket(m_ServerSocket);
            WSACleanup();
        }
     
        int GetNumCore()
        {
            SYSTEM_INFO SystemInfo;
            GetSystemInfo(&SystemInfo);
            num_Cores = SystemInfo.dwNumberOfProcessors;
            return num_Cores;
        }
     
        bool InitIOCP()
        {
            IOCP_SOCKET* m_HandleSocket;
            IOCP_DATA* m_IocpData;
            if(TestVersion()==false)
            {
                printf("require WSA 2.0
    ");
            }
     
            //创建完成端口
            m_CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
            if(m_CompletionPort==NULL)
            {
                printf("create completionport error
    ");
            }
            DWORD threadID=0;
            //创建服务端处理线程
            int n=0;
            for(n=0;n<num_Cores;n++)
            {
                HANDLE threadHandle;
                threadHandle = CreateThread(NULL,0,ServerThread,
                m_CompletionPort,0,&threadID);
     
                if(threadHandle==NULL)
                {
                    printf("create thread error!
    ");
                }
     
                CloseHandle(threadHandle);
            }
                //创建服务端socket
            m_ServerSocket = WSASocket(AF_INET,SOCK_STREAM,0,
                NULL,0,WSA_FLAG_OVERLAPPED);
     
            if(m_ServerSocket == INVALID_SOCKET)
            {
                printf("Init Socket error!
    ");
            }
     
            //初始化socket的网络地址
            sockaddr_in m_addr;
            m_addr.sin_family = AF_INET;
            m_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
            m_addr.sin_port = htons(10001);
            bind(m_ServerSocket,(PSOCKADDR)&m_addr,sizeof(m_addr));
            listen(m_ServerSocket,10);
            SOCKET m_TempSocket;
     
            while(true)
            {
                struct sockaddr_in client_address;
                int address_len = sizeof(client_address);
                //捕获申请链接的socket
                m_TempSocket = WSAAccept(m_ServerSocket,
                    (struct sockaddr*)(&client_address),&address_len,NULL,0);
     
                printf("client IP:%s connected!
    ",inet_ntoa(client_address.sin_addr));
                //给数据分配空间,并加入完成端口队列
                m_HandleSocket = (IOCP_SOCKET*)GlobalAlloc(GPTR,sizeof(IOCP_SOCKET));
                m_HandleSocket->m_socket = m_TempSocket;
                if(CreateIoCompletionPort((HANDLE)m_TempSocket,m_CompletionPort,
                                            (DWORD)m_HandleSocket,0)==NULL)
                {
                    printf("create iocp error!
    ");
                    return false;
                }
     
                m_IocpData = (IOCP_DATA*)GlobalAlloc(GPTR,sizeof(IOCP_DATA));
                ZeroMemory(&(m_IocpData->overlapped),sizeof(OVERLAPPED));
                m_IocpData->sendbytes = 0;
                m_IocpData->recvbytes = 0;
                m_IocpData->dataBuf.len = BUF_SIZE;
                m_IocpData->dataBuf.buf = m_IocpData->buf;
                DWORD Flag = 0;
                DWORD TransBytes = 0;
     
                if(WSARecv(m_TempSocket,&(m_IocpData->dataBuf),1,&TransBytes,
                &Flag,&(m_IocpData->overlapped),NULL)==SOCKET_ERROR)
                {
                    if(WSAGetLastError()!= ERROR_IO_PENDING)
                        printf("recv error
    ");
                }
            }
            return true;
        }
    };
    

      第四部分:主程序调用

    int main()
    {
        NIOCP* S_IOCP=new NIOCP;
        printf("CPU CORES:%d
    ",S_IOCP->GetNumCore());
     
        if(false==S_IOCP->InitIOCP())
        {
            printf("Init IOCP error!
    ");
        }
     
        getch();
        return 0;
    }
    

      

    以上四部份代码都写到一个CPP里面,当然你也可以分开几个CPP来写。运行结果是一样的。

    最后我们启动第一节中TCP的客户端程序来检验一下服务器程序。

    IOCP服务程序

    01

    客户端我们用之前TCP的客户程序

    02

    可以看到,当客户发送链接申请给服务器,并传送数据给服务器后,服务线程将信息回发给客户,然后就继续等待接收新的数据传入。

  • 相关阅读:
    没有功能需求文档就拒绝开发吗?
    用Spring cloud Stream来开发基于MQ消息驱动的微服务
    在Linux上讲Java命令行的作为服务运行
    EF提供的三种查询方式
    C#中sealed关键字
    winform Chart控件 获取鼠标处坐标值方法
    Linq to Entity中连接两个数据库时要注意的问题
    linq中查询列表的使用及iqueryable和list集合之间的转换
    C语言关键字register、extern、static
    DllImport的具体用法
  • 原文地址:https://www.cnblogs.com/xiangshancuizhu/p/3326764.html
Copyright © 2020-2023  润新知