精华内容
下载资源
问答
  • IOCP

    2019-08-31 09:34:37
    IOCP模型与网络编程 一。前言: 在老师分配任务(“尝试利用IOCP模型写出服务端和客户端的代码”)给我时,脑子一片空白,并不知道什么是IOCP模型,会不会是像软件设计模式里面的工厂模式,装饰模式之类的那些呢?...

    IOCP模型与网络编程

    一。前言:
            在老师分配任务(“尝试利用IOCP模型写出服务端和客户端的代码”)给我时,脑子一片空白,并不知道什么是IOCP模型,会不会是像软件设计模式里面的工厂模式,装饰模式之类的那些呢?嘿嘿,不过好像是一个挺好玩的东西,挺好奇是什么东西来的,又是一个新知识啦~于是,开始去寻找一大堆的资料,为这个了解做准备,只是呢,有时还是想去找一本书去系统地学习一下,毕竟网络的资料还是有点零散。话说,本人学习这个模型的基础是,写过一个简单的Socket服务器及客户端程序,外加一个简单的Socket单服务器对多客户端程序,懂一点点的操作系统原理的知识。于是,本着一个学习与应用的态度开始探究这个IOCP是个什么东西。

     

    二。提出相关问题:
           1.  IOCP模型是什么?
           2.  IOCP模型是用来解决什么问题的?它为什么存在?
           3.  使用IOCP模型需要用到哪些知识?
           4.  如何使用IOCP模型与Socket网络编程结合起来?
           5.  学会了这个模型以后与我之前写过的简单的socket程序主要有哪些不同点?

     

    三。部分问题探究及解决:(绝大多数是个人理解,再加上个人是菜鸟,如果有什么不对的地方,欢迎指正)
           1.  什么是IOCP?什么是IOCP模型?IOCP模型有什么作用?
                  1) IOCP(I/O Completion Port),常称I/O完成端口。
                  2) IOCP模型属于一种通讯模型,适用于(能控制并发执行的)高负载服务器的一个技术。
                  3) 通俗一点说,就是用于高效处理很多很多的客户端进行数据交换的一个模型。
                  4) 或者可以说,就是能异步I/O操作的模型。
                  5) 只是了解到这些会让人很糊涂,因为还是不知道它究意具体是个什么东东呢?


    下面我想给大家看三个图:
    第一个是IOCP的内部工作队列图。(整合于《IOCP本质论》文章,在英文的基础上加上中文对照)

    第二个是程序实现IOCP模型的基本步骤。(整合于《深入解释IOCP》,加个人观点、理解、翻译)


    第三个是使用了IOCP模型及没使用IOCP模型的程序流程图。(个人理解绘制)

     

    2.  IOCP的存在理由(IOCP的优点)及技术相关有哪些?
            之前说过,很通俗地理解可以理解成是用于高效处理很多很多的客户端进行数据交换的一个模型,那么,它具体的优点有些什么呢?它到底用到了哪些技术了呢?在Windows环境下又如何去使用这些技术来编程呢?它主要使用上哪些API函数呢?呃~看来我真是一个问题多多的人,跟前面提出的相关问题变种延伸了不少的问题,好吧,下面一个个来解决。

     

    1) 使用IOCP模型编程的优点
           ① 帮助维持重复使用的内存池。(与重叠I/O技术有关)
           ② 去除删除线程创建/终结负担。
           ③ 利于管理,分配线程,控制并发,最小化的线程上下文切换。
           ④ 优化线程调度,提高CPU和内存缓冲的命中率。

    2) 使用IOCP模型编程汲及到的知识点(无先后顺序)
           ① 同步与异步
           ② 阻塞与非阻塞
           ③ 重叠I/O技术
           ④ 多线程
           ⑤ 栈、队列这两种基本的数据结构

    3) 需要使用上的API函数
      ① 与SOCKET相关
           1、链接套接字动态链接库:int WSAStartup(...);
           2、创建套接字库:        SOCKET socket(...);
           3、绑字套接字:          int bind(...);
           4、套接字设为监听状态: int listen(...);
           5、接收套接字:          SOCKET accept(...);
           6、向指定套接字发送信息:int send(...);
           7、从指定套接字接收信息:int recv(...);

      ② 与线程相关
           1、创建线程:HANDLE CreateThread(...);

      ③ 重叠I/O技术相关
           1、向套接字发送数据:    int WSASend(...);
           2、向套接字发送数据包:  int WSASendFrom(...);
           3、从套接字接收数据:    int WSARecv(...);
           4、从套接字接收数据包:  int WSARecvFrom(...);

      ④ IOCP相关
           1、创建完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
           2、关联完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
           3、获取队列完成状态: BOOL WINAPI GetQueuedCompletionStatus(...);
           4、投递一个队列完成状态:BOOL WINAPI PostQueuedCompletionStatus(...);

     

    四。完整的简单的IOCP服务器与客户端代码实例:

     

        // IOCP_TCPIP_Socket_Server.cpp
         
        #include <WinSock2.h>
        #include <Windows.h>
        #include <vector>
        #include <iostream>
         
        using namespace std;
         
        #pragma comment(lib, "Ws2_32.lib")        // Socket编程需用的动态链接库
        #pragma comment(lib, "Kernel32.lib")    // IOCP需要用到的动态链接库
         
        /**
         * 结构体名称:PER_IO_DATA
         * 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
         **/
        const int DataBuffSize  = 2 * 1024;
        typedef struct
        {
            OVERLAPPED overlapped;
            WSABUF databuff;
            char buffer[ DataBuffSize ];
            int BufferLen;
            int operationType;
        }PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;
         
        /**
         * 结构体名称:PER_HANDLE_DATA
         * 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。
         * 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。
         **/
        typedef struct
        {
            SOCKET socket;
            SOCKADDR_STORAGE ClientAddr;
        }PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
         
        // 定义全局变量
        const int DefaultPort = 6000;        
        vector < PER_HANDLE_DATA* > clientGroup;        // 记录客户端的向量组
         
        HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
        DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);
        DWORD WINAPI ServerSendThread(LPVOID IpParam);
         
        // 开始主函数
        int main()
        {
        // 加载socket动态链接库
            WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库
            WSADATA wsaData;    // 接收Windows Socket的结构信息
            DWORD err = WSAStartup(wVersionRequested, &wsaData);
         
            if (0 != err){    // 检查套接字库是否申请成功
                cerr << "Request Windows Socket Library Error!\n";
                system("pause");
                return -1;
            }
            if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){// 检查是否申请了所需版本的套接字库
                WSACleanup();
                cerr << "Request Windows Socket Version 2.2 Error!\n";
                system("pause");
                return -1;
            }
         
        // 创建IOCP的内核对象
            /**
             * 需要用到的函数的原型:
             * HANDLE WINAPI CreateIoCompletionPort(
             *    __in   HANDLE FileHandle,        // 已经打开的文件句柄或者空句柄,一般是客户端的句柄
             *    __in   HANDLE ExistingCompletionPort,    // 已经存在的IOCP句柄
             *    __in   ULONG_PTR CompletionKey,    // 完成键,包含了指定I/O完成包的指定文件
             *    __in   DWORD NumberOfConcurrentThreads // 真正并发同时执行最大线程数,一般推介是CPU核心数*2
             * );
             **/
            HANDLE completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);
            if (NULL == completionPort){    // 创建IO内核对象失败
                cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;
                system("pause");
                return -1;
            }
         
        // 创建IOCP线程--线程里面创建线程池
         
            // 确定处理器的核心数量
            SYSTEM_INFO mySysInfo;
            GetSystemInfo(&mySysInfo);
         
            // 基于处理器的核心数量创建线程
            for(DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2); ++i){
                // 创建服务器工作器线程,并将完成端口传递到该线程
                HANDLE ThreadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL);
                if(NULL == ThreadHandle){
                    cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;
                system("pause");
                    return -1;
                }
                CloseHandle(ThreadHandle);
            }
         
        // 建立流式套接字
            SOCKET srvSocket = socket(AF_INET, SOCK_STREAM, 0);
         
        // 绑定SOCKET到本机
            SOCKADDR_IN srvAddr;
            srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
            srvAddr.sin_family = AF_INET;
            srvAddr.sin_port = htons(DefaultPort);
            int bindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
            if(SOCKET_ERROR == bindResult){
                cerr << "Bind failed. Error:" << GetLastError() << endl;
                system("pause");
                return -1;
            }
         
        // 将SOCKET设置为监听模式
            int listenResult = listen(srvSocket, 10);
            if(SOCKET_ERROR == listenResult){
                cerr << "Listen failed. Error: " << GetLastError() << endl;
                system("pause");
                return -1;
            }
            
        // 开始处理IO数据
            cout << "本服务器已准备就绪,正在等待客户端的接入...\n";
         
            // 创建用于发送数据的线程
            HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);
         
            while(true){
                PER_HANDLE_DATA * PerHandleData = NULL;
                SOCKADDR_IN saRemote;
                int RemoteLen;
                SOCKET acceptSocket;
         
                // 接收连接,并分配完成端,这儿可以用AcceptEx()
                RemoteLen = sizeof(saRemote);
                acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);
                if(SOCKET_ERROR == acceptSocket){    // 接收客户端失败
                    cerr << "Accept Socket Error: " << GetLastError() << endl;
                    system("pause");
                    return -1;
                }
                
                // 创建用来和套接字关联的单句柄数据信息结构
                PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));    // 在堆中为这个PerHandleData申请指定大小的内存
                PerHandleData -> socket = acceptSocket;
                memcpy (&PerHandleData -> ClientAddr, &saRemote, RemoteLen);
                clientGroup.push_back(PerHandleData);        // 将单个客户端数据指针放到客户端组中
         
                // 将接受套接字和完成端口关联
                CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), completionPort, (DWORD)PerHandleData, 0);
         
                
                // 开始在接受套接字上处理I/O使用重叠I/O机制
                // 在新建的套接字上投递一个或多个异步
                // WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务    
                // 单I/O操作数据(I/O重叠)
                LPPER_IO_OPERATION_DATA PerIoData = NULL;
                PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
                ZeroMemory(&(PerIoData -> overlapped), sizeof(OVERLAPPED));
                PerIoData->databuff.len = 1024;
                PerIoData->databuff.buf = PerIoData->buffer;
                PerIoData->operationType = 0;    // read
         
                DWORD RecvBytes;
                DWORD Flags = 0;
                WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
            }
         
            system("pause");
            return 0;
        }
         
        // 开始服务工作线程函数
        DWORD WINAPI ServerWorkThread(LPVOID IpParam)
        {
            HANDLE CompletionPort = (HANDLE)IpParam;
            DWORD BytesTransferred;
            LPOVERLAPPED IpOverlapped;
            LPPER_HANDLE_DATA PerHandleData = NULL;
            LPPER_IO_DATA PerIoData = NULL;
            DWORD RecvBytes;
            DWORD Flags = 0;
            BOOL bRet = false;
         
            while(true){
                bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);
                if(bRet == 0){
                    cerr << "GetQueuedCompletionStatus Error: " << GetLastError() << endl;
                    return -1;
                }
                PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);
                
                // 检查在套接字上是否有错误发生
                if(0 == BytesTransferred){
                    closesocket(PerHandleData->socket);
                    GlobalFree(PerHandleData);
                    GlobalFree(PerIoData);
                    continue;
                }
                
                // 开始数据处理,接收来自客户端的数据
                WaitForSingleObject(hMutex,INFINITE);
                cout << "A Client says: " << PerIoData->databuff.buf << endl;
                ReleaseMutex(hMutex);
         
                // 为下一个重叠调用建立单I/O操作数据
                ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空内存
                PerIoData->databuff.len = 1024;
                PerIoData->databuff.buf = PerIoData->buffer;
                PerIoData->operationType = 0;    // read
                WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
            }
         
            return 0;
        }
         
         
        // 发送信息的线程执行函数
        DWORD WINAPI ServerSendThread(LPVOID IpParam)
        {
            while(1){
                char talk[200];
                gets(talk);
                int len;
                for (len = 0; talk[len] != '\0'; ++len){
                    // 找出这个字符组的长度
                }
                talk[len] = '\n';
                talk[++len] = '\0';
                printf("I Say:");
                cout << talk;
                WaitForSingleObject(hMutex,INFINITE);
                for(int i = 0; i < clientGroup.size(); ++i){
                    send(clientGroup[i]->socket, talk, 200, 0);    // 发送信息
                }
                ReleaseMutex(hMutex);
            }
            return 0;
        }


     

        // IOCP_TCPIP_Socket_Client.cpp
         
        #include <iostream>
        #include <cstdio>
        #include <string>
        #include <cstring>
        #include <winsock2.h>
        #include <Windows.h>
         
        using namespace std;
         
        #pragma comment(lib, "Ws2_32.lib")        // Socket编程需用的动态链接库
         
        SOCKET sockClient;        // 连接成功后的套接字
        HANDLE bufferMutex;        // 令其能互斥成功正常通信的信号量句柄
        const int DefaultPort = 6000;
         
        int main()
        {
        // 加载socket动态链接库(dll)
            WORD wVersionRequested;
            WSADATA wsaData;    // 这结构是用于接收Wjndows Socket的结构信息的
            wVersionRequested = MAKEWORD( 2, 2 );    // 请求2.2版本的WinSock库
            int err = WSAStartup( wVersionRequested, &wsaData );
            if ( err != 0 ) {    // 返回值为零的时候是表示成功申请WSAStartup
                return -1;
            }
            if ( LOBYTE( wsaData.wVersion ) != 2 ||    HIBYTE( wsaData.wVersion ) != 2 ) { // 检查版本号是否正确
                WSACleanup( );
                return -1;
            }
            
        // 创建socket操作,建立流式套接字,返回套接字号sockClient
             sockClient = socket(AF_INET, SOCK_STREAM, 0);
             if(sockClient == INVALID_SOCKET) {
                printf("Error at socket():%ld\n", WSAGetLastError());
                WSACleanup();
                return -1;
              }
         
        // 将套接字sockClient与远程主机相连
            // int connect( SOCKET s,  const struct sockaddr* name,  int namelen);
            // 第一个参数:需要进行连接操作的套接字
            // 第二个参数:设定所需要连接的地址信息
            // 第三个参数:地址的长度
            SOCKADDR_IN addrSrv;
            addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");        // 本地回路地址是127.0.0.1;
            addrSrv.sin_family = AF_INET;
            addrSrv.sin_port = htons(DefaultPort);
            while(SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){
                // 如果还没连接上服务器则要求重连
                cout << "服务器连接失败,是否重新连接?(Y/N):";
                char choice;
                while(cin >> choice && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N')))){
                    cout << "输入错误,请重新输入:";
                    cin.sync();
                    cin.clear();
                }
                if (choice == 'Y'){
                    continue;
                }
                else{
                    cout << "退出系统中...";
                    system("pause");
                    return 0;
                }
            }
            cin.sync();
            cout << "本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n";
         
            send(sockClient, "\nAttention: A Client has enter...\n", 200, 0);
         
            bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);
         
            DWORD WINAPI SendMessageThread(LPVOID IpParameter);
            DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);
         
            HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);  
            HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);  
         
               
            WaitForSingleObject(sendThread, INFINITE);  // 等待线程结束
            closesocket(sockClient);
            CloseHandle(sendThread);
            CloseHandle(receiveThread);
            CloseHandle(bufferMutex);
            WSACleanup();    // 终止对套接字库的使用
         
            printf("End linking...\n");
            printf("\n");
            system("pause");
            return 0;
        }
         
         
        DWORD WINAPI SendMessageThread(LPVOID IpParameter)
        {
            while(1){
                string talk;
                getline(cin, talk);
                WaitForSingleObject(bufferMutex, INFINITE);        // P(资源未被占用)  
                if("quit" == talk){
                    talk.push_back('\0');
                    send(sockClient, talk.c_str(), 200, 0);
                    break;
                }
                else{
                    talk.append("\n");
                }
                printf("\nI Say:(\"quit\"to exit):");
                cout << talk;
                send(sockClient, talk.c_str(), 200, 0);    // 发送信息
                ReleaseSemaphore(bufferMutex, 1, NULL);        // V(资源占用完毕)
            }
            return 0;
        }
         
         
        DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
        {
            while(1){    
                char recvBuf[300];
                recv(sockClient, recvBuf, 200, 0);
                WaitForSingleObject(bufferMutex, INFINITE);        // P(资源未被占用)  
         
                printf("%s Says: %s", "Server", recvBuf);        // 接收信息
                
                ReleaseSemaphore(bufferMutex, 1, NULL);        // V(资源占用完毕)
            }
            return 0;
        }
       

    展开全文
  • iocp

    千次阅读 2013-07-23 11:41:15
    IOCP相关的一些总结 1:在IOCP中投递WSASend返回WSA_IO_PENDING的时候,表示异步投递已经成功,但是稍后发送才会完成。这其中涉及到了三个缓冲区。 网卡缓冲区,TCP/IP层缓冲区,程序缓冲区。 情况一:调用WSASend...
    http://laokaddk.blog.51cto.com/blog/368606/610780
    IOCP相关的一些总结
    1:在IOCP中投递WSASend返回WSA_IO_PENDING的时候,表示异步投递已经成功,但是稍后发送才会完成。这其中涉及到了三个缓冲区。 网卡缓冲区,TCP/IP层缓冲区,程序缓冲区。 情况一:调用WSASend发送正确的时候(即立即返回,且没有错误),TCP/IP将数据从程序缓冲区中拷贝到TCP/IP层缓冲区中,然后不锁定该程序缓冲区,由上层程序自己处理。TCP/IP层缓冲区在网络合适的时候,将其数据拷贝到网卡缓冲区,进行真正的发送。 情况二:调用WSASend发送错误,但是错误码是WSA_IO_PENDING的时候,表示此时TCP/IP层缓冲区已满,暂时没有剩余的空间将程序缓冲区的数据拷贝出来,这时系统将锁定用户的程序缓冲区,按照书上说的WSASend指定的缓冲区将会被锁定到系统的非分页内存中。直到TCP/IP层缓冲区有空余的地方来接受拷贝我们的程序缓冲区数据才拷贝走,并将给IOCP一个完成消息。 情况三:调用WSASend发送错误,但是错误码不是WSA_IO_PENDING,此时应该是发送错误,应该释放该SOCKET对应的所有资源。
    2:在IOCP中投递WSARecv的时候,情况相似。 情况一:调用WSARecv正确,TCP/IP将数据从TCP/IP层缓冲区拷贝到缓冲区,然后由我们的程序自行处理了。清除TCP/IP层缓冲区数据。 情况二:调用WSARecv错误,但是返回值是WSA_IO_PENDING,此时是因为TCP/IP层缓冲区中没有数据可取,系统将会锁定我们投递的WSARecv的buffer,直到TCP/IP层缓冲区中有新的数据到来。 情况三:调用WSARecv错误,错误值不是WSA_IO_PENDING,此时是接收出错,应该释放该SOCKET对应的所有资源。
    在以上情况中有几个非常要注意的事情: 系统锁定非分页内存的时候,最小的锁定大小是4K(当然,这个取决于您系统的设置,也可以设置小一些,在注册表里面可以改,当然我想这些数值微软应该比我们更知道什么合适了),所以当我们投递了很多WSARecv或者WSASend的时候,不管我们投递的Buffer有多大(0除外),系统在出现IO_PENGDING的时候,都会锁定我们4K的内存。这也就是经常有开发者出现WSANOBUF的情况原因了。
    我们在解决这个问题的时候,要针对WSASend和WSARecv做处理 1:投递WSARecv的时候,可以采用一个巧妙的设计,先投递0大小Buf的WSARecv,如果返回,表示有数据可以接收,我们开启真正的recv将数据从TCP/IP层缓冲区取出来,直到WSA_IO_PENGDING. 2:对投递的WSARecv以及WSASend进行计数统计,如果超过了我们预定义的值,就不进行WSASend或者WSARecv投递了。 3:现在我们应该就可以明白为什么WSASend会返回小于我们投递的buffer空间数据值了,是因为TCP/IP层缓冲区小于我们要发送的缓冲区,TCP/IP只会拷贝他剩余可被Copy的缓冲区大小的数据走,然后给我们的WSASend的已发送缓冲区设置为移走的大小,下一次投递的时候,如果TCP/IP层还未被发送,将返回WSA_IO_PENGDING。 4:在很多地方有提到,可以关闭TCP/IP层缓冲区,可以提高一些效率和性能,这个从上面的分析来看,有这个可能,要实际的网络情况去实际分析了。
    ==================
    关于数据包在应用层乱序问题就不多说了(IOCP荒废了TCP在传输层辛辛苦苦保证的有序)。
    这无关紧要,因为iocp要管理上千个SOCKET,每个SOCKET的读请求、写请求分别保证串行即可。
    =============
    关于GetQueuedCompletionStatus的返回值判断:
    我给超时值传的是0,直接测试,无须等待。
    这里我们关心这几个值:
    第二个参数所传回的byte值
    第三个参数所传回的complete key值 ——PER HANDLE DATA
    第四个参数所传回的OVERLAPPED结构指针 ——PER IO DATA
    系统设置的ERROR值。
    在超时情况下,byte值返回0,per handle data值是-1,per io data为NULL
    1.如果返回FALSE
        one : iocp句柄在外部被关闭。
       WSAGetLastError返回6(无效句柄),byte值返回0,per handle data值是-1,per io data为NULL
        two: 我们主动close一个socket句柄,或者CancelIO(socket)(且此时有未决的操作)
        WSAGetLastError返回995(由于线程退出或应用程序请求,已放弃 I/O 操作)
       byte值为0,
       per handle data与per io data正确传回。
       three:对端强退(且此时本地有未决的操作)
       WSAGetLastError返回64(指定的网络名不再可用)
      byte值为0,per handle data与per io data正确传回 
    2.如果返回TRUE【此时一定得到了你投递的OVERLAP结构】
        one:  我接收到对端数据,然后准备再投递接收请求;但此期间,对端关闭socket。
       WSARecv返回错误码10054:远程主机强迫关闭了一个现有的连接。
    TODO TODO
       从网上搜到一个做法,感觉很不错:
    如果返回FALSE, 那么:如果OVERLAP为空,那一定是发生了错误(注意:请排除TIMEOUT错误);
    如果OVERLAP不为空,有可能发生错误。不用管它,这里直接投递请求;如果有错,WSARecv将返回错误。关闭连接即可。
    =================================================================
    http://www.cppblog.com/Fox/archive/2011/04/19/95975.html
    在使用IOCP时,最重要的几个API就是GetQueueCompeltionStatus、WSARecv、WSASend,数据的I/O及其完成状态通过这几个接口获取并进行后续处理。
    GetQueueCompeltionStatus attempts to dequeue an I/O completion packet from the specified I/O completion port. If there is no completion packet queued, the function waits for a pending I/O operation associated with the completion port to complete.
    BOOL WINAPI GetQueuedCompletionStatus( __in   HANDLE CompletionPort,
      __out  LPDWORD lpNumberOfBytes,
      __out  PULONG_PTR lpCompletionKey,
      __out  LPOVERLAPPED *lpOverlapped,
      __in   DWORD dwMilliseconds ); If the function dequeues a completion packet for a successful I/O operation from the completion port, the return value is nonzero. The function stores information in the variables pointed to by the lpNumberOfBytes, lpCompletionKey, and lpOverlapped parameters.
    除了关心这个API的in & out(这是MSDN开头的几行就可以告诉我们的)之外,我们更加关心不同的return & out意味着什么,因为由于各种已知或未知的原因,我们的程序并不总是有正确的return & out。
    If *lpOverlapped is NULL and the function does not dequeue a completion packet from the completion port, the return value is zero. The function does not store information in the variables pointed to by the lpNumberOfBytes and lpCompletionKey parameters. To get extended error information, call GetLastError. If the function did not dequeue a completion packet because the wait timed out, GetLastError returns WAIT_TIMEOUT.
    假设我们指定dwMilliseconds为INFINITE。
    这里常见的几个错误有:
    WSA_OPERATION_ABORTED (995): Overlapped operation aborted.
    由于线程退出或应用程序请求,已放弃I/O 操作。
    MSDN: An overlapped operation was canceled due to the closure of the socket, or the execution of the SIO_FLUSH command in WSAIoctl. Note that this error is returned by the operating system, so the error number may change in future releases of Windows.
    成因分析:这个错误一般是由于peer socket被closesocket或者WSACleanup关闭后,针对这些socket的pending overlapped I/O operation被中止。
    解决方案:针对socket,一般应该先调用shutdown禁止I/O操作后再调用closesocket关闭。
    严重程度:轻微易处理。
    WSAENOTSOCK (10038): Socket operation on nonsocket.
    MSDN: An operation was attempted on something that is not a socket. Either the socket handle parameter did not reference a valid socket, or for select, a member of an fd_set was not valid.
    成因分析:在一个非套接字上尝试了一个操作。
    使用closesocket关闭socket之后,针对该invalid socket的任何操作都会获得该错误。
    解决方案:如果是多线程存在对同一socket的操作,要保证对socket的I/O操作逻辑上的顺序,做好socket的graceful disconnect。
    严重程度:轻微易处理。
    WSAECONNRESET (10054): Connection reset by peer.
    远程主机强迫关闭了一个现有的连接。
    MSDN: An existing connection was forcibly closed by the remote host. This normally results if the peer application on the remote host is suddenly stopped, the host is rebooted, the host or remote network interface is disabled, or the remote host uses a hard close (see setsockopt for more information on the SO_LINGER option on the remote socket). This error may also result if a connection was broken due to keep-alive activity detecting a failure while one or more operations are in progress. Operations that were in progress fail with WSAENETRESET. Subsequent operations fail with WSAECONNRESET.
    成因分析:在使用WSAAccpet、WSARecv、WSASend等接口时,如果peer application突然中止(原因如上所述),往其对应的socket上投递的operations将会失败。
    解决方案:如果是对方主机或程序意外中止,那就只有各安天命了。但如果这程序是你写的,而你只是hard close,那就由不得别人了。至少,你要知道这样的错误已经出现了,就不要再费劲的继续投递或等待了。
    严重程度:轻微易处理。
    WSAECONNREFUSED (10061): Connection refused.
    由于目标机器积极拒绝,无法连接。
    MSDN: No connection could be made because the target computer actively refused it. This usually results from trying to connect to a service that is inactive on the foreign host—that is, one with no server application running.
    成因分析:在使用connect或WSAConnect时,服务器没有运行或者服务器的监听队列已满;在使用WSAAccept时,客户端的连接请求被condition function拒绝。
    解决方案:Call connect or WSAConnect again for the same socket. 等待服务器开启、监听空闲或查看被拒绝的原因。是不是长的丑或者钱没给够,要不就是服务器拒绝接受天价薪酬自主创业去了?
    严重程度:轻微易处理。
    WSAENOBUFS (10055): No buffer space available.
    由于系统缓冲区空间不足或列队已满,不能执行套接字上的操作。
    MSDN: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.
    成因分析:这个错误是我查看错误日志后,最在意的一个错误。因为服务器对于消息收发有明确限制,如果缓冲区不足应该早就处理了,不可能待到send/recv失败啊。而且这个错误在之前的版本中几乎没有出现过。这也是这篇文章的主要内容。像connect和accept因为缓冲区空间不足都可以理解,而且危险不高,但如果send/recv造成拥堵并恶性循环下去,麻烦就大了,至少说明之前的验证逻辑有疏漏。
    WSASend失败的原因是:The Windows Sockets provider reports a buffer deadlock. 这里提到的是buffer deadlock,显然是由于多线程I/O投递不当引起的。
    解决方案:在消息收发前,对最大挂起的消息总的数量和容量进行检验和控制。
    严重程度:严重。
    ==================================
    GetQueueCompeltionStatus 恐怖的一沓糊涂,运行一段时间就蹦出些莫名的错误,我这辈子是不会再用了。。。socket万岁。 
    BOOL bSuccess = GetQueuedCompletionStatus(m_hCompletionPort, &dwNumberBytes, &CompletionKey, (LPOVERLAPPED*)&overlap, 10);//10ms for cpu eat. 
    time_check(); 
    if (overlap) personal = overlap->content; 
    if (bSuccess == FALSE) { DWORD LastError = GetLastError(); if (LastError == WAIT_TIMEOUT) continue; 
    // 2 - 系统找不到指定的文件。 // 121 - 信号灯超时时间已到。 
    // 1450 - 系统资源不足,无法完成请求的服务。 // 995 - 由于线程退出或应用程序请求,已放弃 I/O 操作。 
    // 64 - 指定的网络名不再可用。 // 10053 - 您的主机中的软件放弃了一个已建立的连接。 // 10054 - 远程主机强迫关闭了一个现有的连接 // 10058 - 由于以前的关闭调用,套接字在那个方向已经关闭,发送或接收数据的请求没有被接受。 
    // 0 - 操作成功完成。 // 997 - 重叠 I/O 操作在进行中。 // 998 - 内存分配访问无效。 (when fread() filesize > 3G) 
    ========================================
    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,437
精华内容 2,174
热门标签
关键字:

iocp