好久没在windows下开发,突然有需要用一下,发现落伍了
今天在网上学习了以下网络IO的各种方法, 总结以下,就是给各链接地址,今后好查!
建议先看这篇, 生动,便于理解:
http://www.cnblogs.com/curo0119/p/8469794.html
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