关于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
- Python中赋值、浅拷贝与深拷贝
- git 简易使用说明
- 开发篇-MySQL分区(一)
- Establishing SSL connection without server's identity verification is not recommended. According to
- Django-认证系统
- javascript:双链表-插入排序
- javascript:二叉搜索树 实现
- 自然语言处理 语言模型介绍
- 口水先擦干!从大数据看外卖如何拯救“忙”与“宅”
- javascript:巧用eval函数组装表单输入项为json对象
- Django——model基础
- java学习:日期的运算
- ORACLE:写Function时,传入参数变量名的注意事项
- spring boot 登录注册 demo (二) -- 数据库访问
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 教你 Linux 免密登录配置
- Linux阅码场 - Linux内核月报(2020年08月)
- 事务的本质和死锁的原理
- 深度神经网络conda环境下载
- 隧道构建:端口转发的原理和实现
- SAP Spartacus注入自定义的CurrentProductService
- Redis系列(十一)redis命令全集
- Jinkens+gitlab针对k8s集群实现CI/CD
- Vue 踩过的坑
- Java TCP/UDP/HttpClient简例
- 让你设计实现一个签到功能,到底用MySQL还是Redis?
- 如何防止MySQL重复插入数据,这篇文章会告诉你
- Spring AOP注解开发
- 快速学习-Jenkins CLI凭据
- 快速学习-Jenkins CLI任务