关于windows下各种网络IO操作模型的学习总结

时间:2019-02-16
本文章向大家介绍关于windows下各种网络IO操作模型的学习总结,主要包括关于windows下各种网络IO操作模型的学习总结使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

好久没在windows下开发,突然有需要用一下,发现落伍了

今天在网上学习了以下网络IO的各种方法, 总结以下,就是给各链接地址,今后好查!

 

REF:   https://blog.csdn.net/ithzhang/article/details/8496232
windows 套接字的操作模型

1) 同步模型             直接使用send/recv函数进行发送/接收操作


2) select模型            使用select函数查询套接字是否满足发送/读取条件, 再使用send/recv函数进行发送/接收操作

   REF: https://blog.csdn.net/skyandcode/article/details/8646630
3) WSAAsyncSelect模型   接收以 Windows 消息为基础的网络事件通知,使用widows窗口消息WM_SOCKET回调函数中来处理网络事件
                        使用WSAAsyncSeltct(s, hwnd,WM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE)注册事件类型
                        消息处理函数中:
                            ● wParam参数指发生了一个网络事件的套接字
                            ● lParam参数低字(WORD)为网络事件,可使用宏WSAGETSELECTERROR(lParam)
                            ● lParam参数高字(WORD)为错误代码,可使用宏WSAGETSELECTEVENT(lParam)

    REF:https://www.cnblogs.com/HPAHPA/p/7819213.html                             
4) WSAEventSelect模型   网络事件会投递至一个"事件对象句柄",而非投递至一个窗口
                        WSAEVENT newEvent = WSACreateEvent();
                        WSAEventSelect(fd,newEvent,FD_ACCEPT| FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);                        
                        WSAEVENT eventObjs[WSA_MAXIMUM_WAIT_EVENTS];//下面的等待同等待躲过普通hEvt对象功能类似
                        event[0]=newEvent;
                        eventObjs_count =1;
                        //注意下面的index, 返回发生发生了事件的WSAEVENT的索引, 其起始值为WSA_WAIT_EVENT_0                        
                        index = WSAWaitForMultipleEvents(eventObjs_count,eventObjs,FALSE,WSA_INFINITE,FALSE); 
                        
                        //查看发生的网络事件类型,确定发生网络事件的套接字
                        WSANETWORKEVENTS networkEvents;
                        SOCKET Socket[WSA_MAXIMUM_WAIT_EVENTS];//64
                        Socket[0] = fd;
                        Socket_count =1;
                        WSAEnumNetworkEvents(Socket[index-WSA_WAIT_EVENT_0],event[index-WSA_WAIT_EVENT_0],&networkEvents);                        
                        
                        //查看发生的网络事件类型,确定发生网络事件的套接字
                        WSANETWORKEVENTS networkEvents;
                        long lNetworkEvents;
                        WSAEnumNetworkEvents(Socket[index-WSA_WAIT_EVENT_0],event[index-WSA_WAIT_EVENT_0],&networkEvents);                        

    REF:https://blog.csdn.net/ithzhang/article/details/8496232                    
5)重叠IO模型          从IO收/发角度来看: 重叠IO模型与前面介绍的Select模型、和WSAEventSelect模型都不同。
                        select模型利用select函数主动检查系统中套接字是否满足可读条件
                        WSAAsyncSelect模型和WSAEventSelect模型则被动等待系统的通知
                        以上模型中实际的IO操作还是同步的,只不过可以通过事件来通知应用层,IO操作的最佳时机
                        而重叠IO模型会在调用recv后立即返回。等数据准备好后再通知应用程序
                        
                        Winsock1 中创建使用套接字 API创建一个重叠的套接字,使用 Win32 文件 I/O API ReadFile,ReadFileEx、 WriteFile、 WriteFileEx 套接字句柄上执行重叠的 I/O
                        Winsock2 中创建一个重叠的套接字 WSASocket 使用 WSA_FLAG_OVERLAPPED 标志,或只需使用套接字socket函数
                                                
                        Winsock2套接字重叠IO模型主要有一下相关函数:
                             WSASocket():创建套接字。 
                             WSASend和WSASendTo:发送数据。
                             WSARecv和WSARecvFrom:接收数据。
                             WSAIoctl:控制套接字模式。
                             AcceptEx:接受连接
                        ***套接字上使用重叠IO模型,在创建套接字时,必须使用WSA_FLAG_OVERLAPPED标志创建
                            SOCKET s =WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP, NULL,0,WSA_FLAG_OVERLAPPED);
                        *** 当使用socket函数创建套接字时,会默认设置WSA_FLAG_OVERLAPPED标志

                        应用程序调用WSARecv和WSARecvFrom函数接收数据,这两个函数的最后两个参数如下:
                            lpOverlapped:指向WSAOVERLAPPED结构指针。
                            lpCompletionROUTINE:完成例程
                            
                            i)  如果lpOverlapped和lpCompletionROUTINE都为NULL,则该套接字作为同步IO套接字使用
                            ii) 应用程序调用WSARecv的时间晚于数据到达的时间,则数据到达时会直接放入用户的接收缓冲区
                                此时: 函数返回值为0。lpNumberOfBytesRecvd参数指明接收数据的字节数
                                应用程序调用WSARecv的时间先于数据到达的时间时,函数返回SOCKET_ERROR值。WSAGetLastError返回WSA_IO_PENDING                                
                                
                            ii) 如果lpCompletionRoutine参数为NULL,当接收完成时,lpOverlapped参数的事件变为触发状态(有信号)
                                应用程序中可以调用WSAWaitForMultipleEvents或者是WSAGetOverlappedResult等待该事件
                                
                            
                        说明: lpOverlapped指针指向的WSAOVERLAPPED结构中,用程序可以执行一下步骤将一个事件对象与套接字关联起来:
                                1:调用WSACreateEvent创建事件对象。
                                2:将该事件赋值给WSAOVERLAPPED结构的hEvent字段。
                                3:使用该重叠结构,调用WSASend或WSARecv函数。
                                当重叠操作完成时,重叠IO结构的事件对象变为已触发状态。
                                可以调用WSAWaitForMultipleEvents函数等待该事件发生,注意它最多只能等待64个事件对象
                                WSAGetOverlappedResult函数返回指定socketfd返上重叠IO的结构
 
 6)重叠IO模型完成例程    在5)中仅说明了重叠IO结构WSAOVERLAPPED的事件对象的hEvent字段来监控IO事件的触发信号,没有讲完成例程(回调)的使用
                          以下参考: https://blog.csdn.net/xiaoyafang123/article/details/53540708
                          //完成例程回调函数原型
                          Void CALLBACK _CompletionRoutineFunc( DWORD dwError,         // 标志咱们投递的重叠操作,比如WSARecv,完成的状态是什么
                                                              DWORD cbTransferred,     // 指明了在重叠操作期间,实际传输的字节量是多大
                                                              LPWSAOVERLAPPED lpOverlapped, // 参数指明传递到最初的IO调用内的一个重叠  结构
                                                              DWORD dwFlags                  // 返回操作结束时可能用的标志(一般没用)
                                                              );
                          //接收函数入口参数中指定例程回调函数                                      
                            int WSARecv( SOCKET s,                      // 当然是投递这个操作的套接字
                                        LPWSABUF lpBuffers,            // 接收缓冲区,与Recv函数不同,这里是一个由WSABUF结构构成的数组
                                        DWORD dwBufferCount,        // 数组中WSABUF结构的数量,设置为1即可
                                        LPDWORD lpNumberOfBytesRecvd,  // 如果接收操作立即完成,这里会返回函数调用所接收到的字节数
                                        LPDWORD lpFlags,             // 设置为0 即可
                                        LPWSAOVERLAPPED lpOverlapped,  // “绑定”的重叠结构
                                        LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 我们的完成例程函数的指针                                                                     
                                        );

                          //例子:
                            SOCKET s;
                            WSABUF DataBuf;           // 定义WSABUF结构的缓冲区
                            // 初始化一下DataBuf
                            #define DATA_BUFSIZE 4096
                            char buffer[DATA_BUFSIZE];
                            ZeroMemory(buffer, DATA_BUFSIZE);
                            DataBuf.len = DATA_BUFSIZE;
                            DataBuf.buf = buffer;
                            DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;
                            
                            // 重叠结构,每个重叠操作都得绑定一个
                            WSAOVERLAPPED AcceptOverlapped ;// 如果要处理多个操作,这里当然需要一个
                            ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));

                            // 假设我们的_CompletionRoutine函数已经定义好了
                            WSARecv(s, &DataBuf, dwBufferCount, &dwRecvBytes, &Flags, &AcceptOverlapped, _CompletionRoutine);使用WSARecv来把我们的完成例程函数绑定上了
                            
                            当你调用了SleepEx、WaitForSingleObjectEx等函数“进入阻塞状态”后,线程被设置为"可警告的"等待状态, 系统就会从APC队列中取出需要回调的函数唤醒该线程
                            DWORD WSAAPI WSAWaitForMultipleEvents( 
                                          DWORD cEvents,                        //WSA_MAXIMUM_WAIT_EVENTS是句柄最大值 64
                                          const WSAEVENT FAR * lphEvents,     //an array of event object handles.
                                            BOOL fWaitAll,                        //TURE:当lphEvents的所有对象有信号的时候函数返回。FALSE:当任意一个事件对象有信号时函数返回
                                          DWORD dwTimeout,                    //The time-out interval,秒为单位
                                          BOOL fAlertable );                    //指定当系统将一个输入/输出完成例程放入队列以供执行时, 函数返回的时机
                                                                                // fAlertable=TRUE:  当该函数返回时完成例程已经被执行,函数返回值为:WAIT_IO_COMPLETION,说明完成例程已经被调用。否则就说明发生了错误
                                                                                // fAlertable=FALSE: 当该函数返回时完成例程还没有执行

                                                                                
                            //SleepEx不会检查event object handles, 如果超时或者发生了发生IO回调,那么就返回
                            DWORD SleepEx{
                                            DWORD dwMilliseconds,    //等待时间,ms为单位,   INFINITE说明函数将无限等待
                                            BOOL bAlertable            //函数的返回方式1、FALSE,在不调用超时的情况下是不返回的。2、TRUE 调用超时或者发生IO回调,那么就返回。
                                        };                          //如果发生了IO完成回调,那么函数就会返回,返回值是:WAIT_IO_COMPLETION

                            
                            HeapAlloc是一个Windows API函数。它用来在指定的堆上分配内存,并且分配后的内存不可移动。它分配的内存不能超过4MB。函数原型是:
                                      LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes);
                                      第一个参数可以使用Windows API函数GetProcessHeap返回调用进程的默认堆句柄
                            HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem);    //REF:https://blog.csdn.net/caoshiying/article/details/52876090
                
    REF: https://blog.csdn.net/smartfox80/article/details/41823101                
7)完成端口IO模型        WSAWaitForMultipleEvents最多只能监控64个事件对象, 当需要监控更多的套接字对象时,可以采用用完成端口模型
                        完成端口模型需要建立自己的工作线程池,一般等于CPU内核数量
                        完成端口是一种内核对象, 创建例子:            
                                         HANDLE  hIoPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );
                        IO对象hFile须采用WSA_FLAG_OVERLAPPED 标志, 并通过 CreateIoCompletionPort函数与完成端口是一种内核对象进行关联
                                        HANDLE h = CreateIoCompletionPort(hFile, hIoPort, completionKey, 0);
                                        assert(hIoPort == h);    //没错, 当CreateIoCompletionPort第二个参数补位NULL时,返回值等于入口参数的hIoPort
                        线程函数中,调用GetQueuedCompletionStatus来捕捉指定oCompletionPort对象hIoPort,来捕获hIoPort对象上关联的IO事件,函数原型:
                                BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytes, PULONG_PTR lpCompletionKey, LPOVERLAPPED *lpOverlapped, DWORD dwMilliseconds);
                                函数从完成端口取出一个成功I/O操作的完成包,返回值为TRUE(非0)
                        应用层中,可以使用PostQueuedCompletionStatus函数,向工作者线程都发送—个指定的完成数据包
                                 PostQueuedCompletionStatus函数提供了一种方式来与线程池中线程进行通信
                                 我理解其实就是模拟内核系统,人为地唤醒IO工作线程,这时GetQueuedCompletionStatus参数返回值 就是通过PostQueuedCompletionStatus投送的参数信息
                                 要唤醒为线程池中的每个线程, 我们需要调用N次PostQueuedCompletionStatus,N为IO工作线程的数量

                        MSDN上面有关GetQueuedCompletionStatus返回值的说明:
                        ------------------第1种情况 
                        如果函数从完成端口取出一个成功I/O操作的完成包,返回值为非0。
                        函数在指向lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped的参数中存储相关信息。

                        ------------------第2种情况
                        如果 *lpOverlapped为空并且函数没有从完成端口取出完成包,返回值则为0。
                        函数则不会在lpNumberOfBytes and lpCompletionKey所指向的参数中存储信息。
                        调用GetLastError可以得到一个扩展错误信息。
                        如果函数由于等待超时而未能出列完成包,GetLastError返回WAIT_TIMEOUT.

                        ------------------第3种情况
                        如果 *lpOverlapped不为空并且函数从完成端口出列一个失败I/O操作的完成包,返回值为0。
                        函数在指向lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped的参数指针中存储相关信息。
                        调用GetLastError可以得到扩展错误信息 。

                        -----------------第4种情况
                        如果关联到一个完成端口的一个socket句柄被关闭了
                        则GetQueuedCompletionStatus返回ERROR_SUCCESS(也是0),并且lpNumberOfBytes等于0