winsock2 中引入了重叠I/O(Overlapped I/O)的概念并且要求所有的传输协议提供者都支持这一功能。  他的功能高于前面我们提过的三种,但是最强悍的还是我们后面要说的完成端口。

基本原理:让应用程序使用一个重叠的数据结构,一次投递一个活多个winsock I/O请求,针对那些提交的清酒,在他们完成之后,应用程序可为他们提供服务。  应用程序可通过ReadFile和WriteFile两个函数执行I/O操作。 要注意:重叠I/O仅能在由WSASocket函数打开的套接字上使用。要想在一个套接字上使用重叠I/O,首先必须使用 WSA_FLAG_OVERLAPPED 这个标志。

[cpp] view plaincopy
  1. SOCKET s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

当你创建套接字的时候,你使用socket函数,她会默认设置WSA_FLAG_OVERLAPPED 标志。

ok,现在我们已经成功的建立了一个套接字,同时将其他与一个本地接口绑定到一起后就可以开始进行重叠I/O操作了。在这里,我们以前用的send,recv等函数将被改变成为WSASend,WSARecv。

来看一下这些函数:

WSASend             // sends data on a connected socket. 提供一个指向已填充的数据缓冲区的指针。
 WSASendTo        // sends data to a specific destination, using overlapped I/O where applicable.
 WSARecv        // receives data from a connected socket.
 WSARecvFrom      //receives data on a socket and stores the source address.  提供存放接收数据的缓冲区
 WSAIoctl      //allows for miscellaneous control of a socket.   还可以使用重叠I/O操作的延迟完成特性。
 AcceptEx   //accepts a new connection, returns the local and remote address, and receives the first block of data sent by the client application.
 TrnasmitFile   //transmits file data over a connected socket handle. uses the operating system's cache manager to retrieve the file data, and provides high-performance file data transfer over sockets.

这里面WS_IO_PENDING 是最常见的返回值,这是说明我们的重叠函数调用成功了,但是I/O操作还没有完成。

如果和一个WSAOVERLAPPED结构一起来调用这些函数,那么函数会立即完成并返回,无论套接字是否是阻塞模式。判断I/O请求是否成功的方法有两个,分别是:

1、等待   事件对象通知。

2、通过 完成例程。

我们谈第一个,事件通知:

在重叠函数的参数中都有一个参数是:Overlapped,我们可以假设是把我们的WSARecv这样的操作“绑定”到这个重叠结构上,提交一个请求,而不是将错做立即完成,其他的事情交给重叠结构去做,而其中的重叠结构又要与windows的事件对象“绑定”到一起,这样我们调用玩WSARecv以后就OK了,等到重叠操作完成后自然会有与之对应的时间来通知我们操作完成了,然后我们就可以来根据重叠操作的结果取得我们想要的数据了。

重叠I/O的事件通知方法要求将win32事件对象与WSOVERLAPPED结构关联在一起,当I/O操作完成后,时间的状态会变成“已传信”状态,也就是激发状态。

来看一下WSAOVERLAPPED结构:

[cpp] view plaincopy
  1. typedef struct _WSAOVERLAPPED {
  2. DWORD    Internal;
  3. DWORD    InternalHigh;
  4. DWORD    Offset;
  5. DWORD    OffsetHigh;//上面参数都是由系统在内部使用,不是由应用程序直接进行处理或使用。
  6. WSAEVENT hEvent;//允许应用程序将一个事件对象句柄同一个套接字关联起来。
  7. } WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;
  8. typedef struct _WSAOVERLAPPED {
  9. ULONG_PTR Internal;
  10. ULONG_PTR InternalHigh;
  11. union {
  12. struct {
  13. DWORD Offset;
  14. DWORD OffsetHigh;
  15. };    PVOID Pointer;
  16. };
  17. HANDLE hEvent;
  18. } WSAOVERLAPPED,  *LPWSAOVERLAPPED;

当重叠I/O请求完成后,应用程序要负责取回重叠I/O操作的结果,一个重叠请求操作最终完成后,在事件通知方法中,winsock会更改与一个WSAOVERLAPPED结构对应的一个事件对象的事件传信状态, 将它从“未传信”变为“已传信”。    由于一个事件对象已分配给WSAOVERLAPPED结构,所以只需要简单滴调用WSAWaitForMultipleEvents函数,从而判断出一个重叠I/O调用在什么时候完成。

还有一个函数是取得重叠结构,他是WSAGetOverlappedResult函数,他是在发现一次重叠请求完成后才可以执行。用来判断这个重叠调用到底是成功还是失败。

[cpp] view plaincopy
  1. BOOL WSAAPI WSAGetOverlappedResult(
  2. __in          SOCKET s,//指定在重叠操作开始的时候,与之对应的那个套接字
  3. __in          LPWSAOVERLAPPED lpOverlapped,//一个指针,对应于在重叠操作开始时。
  4. __out         LPDWORD lpcbTransfer,//一个指针,对应一个DWORD变量,负责接收一次重叠发送/接收操作时机传输的字节数。
  5. __in          BOOL fWait,//用于决定函数是否应该等待一次待解决的重叠操作完成。TRUE:除非操作完成,否则函数不会返回。 FALSE:并且函数处于待解决状态,那么函数返回FALSE,同时返回WSAIOINCOMPLETE错误。   目前,这个参数无论设置什么都没有任何效果。
  6. __out         LPDWORD lpdwFlags//一个指针,指向DWORD负责接收结果标志。
  7. );

当函数调用成功那么就会返回TRUE, 表示重叠操作成功,并且lpcbTransfer参数指向的值已进行了更新。

如果反回了FALSE,那么可能是由于某种原因造成了错误:

1、重叠I/O已经完成,但是又错误;

2、重叠I/O操作仍处于待解决状态;

3、重叠操作的完成状态不能判断,因为函数的参数存在错误。

当函数失败后,lpcbTransfer参数指向的值不会进行更新,而且我们的应用程序应调用WSAGerLastError函数来调查到底是什么原因造成的。

OK,下面来看一下重叠I/O的编程步骤:

1、创建套接字,开始在指定的端口上监听连接请求。

[cpp] view plaincopy
  1. SOCKET         ListenSocket,             // 监听套接字
  2. AcceptSocket;             // 与客户端通信的套接字
  3. WSAOVERLAPPED  AcceptOverlapped;     // 重叠结构一个
  4. WSABUF     DataBuf[DATA_BUFSIZE] ;      
  5. WSADATA wsaData;
  6. WSAStartup(MAKEWORD(2,2),&wsaData);
  7. ListenSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);  //创建TCP套接字
  8. SOCKADDR_IN ServerAddr;                           //分配端口及协议族并绑定
  9. ServerAddr.sin_family=AF_INET;                               
  10. ServerAddr.sin_addr.S_un.S_addr  =htonl(INADDR_ANY);         
  11. ServerAddr.sin_port=htons(8888);
  12. bind(ListenSocket,(LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)); // 绑定套接字
  13. listen(ListenSocket, 5);               

2、接受一个客户端进入的连接请求。

[cpp] view plaincopy
  1. AcceptSocket = accept (ListenSocket, NULL,NULL) ;
  2. 当然,这里是我偷懒,如果想要获得连入客户端的信息(记得论坛上也常有人问到),accept的后两个参数就不要用NULL,而是这样
  3. SOCKADDR_IN ClientAddr;                   // 定义一个客户端得地址结构作为参数
  4. int addr_length=sizeof(ClientAddr);
  5. AcceptSocket = accept(ListenSocket,(SOCKADDR*)&ClientAddr, &addr_length);
  6. // 于是乎,我们就可以轻松得知连入客户端的信息了
  7. LPCTSTR lpIP =  inet_ntoa(ClientAddr.sin_addr);      // IP
  8. UINT nPort = ClientAddr.sin_port;                      // Port

3、为接受的套接字创建一个WSAOVERLAPPED结构,并给这个结构分配一个事件对象句柄,同时将该事件对象句柄分配给一个事件数组,以便稍后WSAWaitForMultipleEvents函数使用。

[cpp] view plaincopy
  1. WSAEVENT  EventArray[WSA_MAXIMUM_WAIT_EVENTS]; 
  2. DWORD     dwEventTotal = 0,            // 程序中事件的总数
  3. dwRecvBytes = 0,            // 接收到的字符长度
  4. Flags = 0;                    // WSARecv的参数
  5. #define DATA_BUFSIZE     4096          // 接收缓冲区大小
[cpp] view plaincopy
  1. // 创建一个事件
  2. // dwEventTotal可以暂时先作为Event数组的索引
  3. EventArray[dwEventTotal] = WSACreateEvent();     
  4. ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));      // 置零
  5. AcceptOverlapped.hEvent = EventArray[dwEventTotal];            // 关联事件
  6. char buffer[DATA_BUFSIZE];
  7. ZeroMemory(buffer, DATA_BUFSIZE);
  8. DataBuf.len = DATA_BUFSIZE;
  9. DataBuf.buf = buffer;                          // 初始化一个WSABUF结构
  10. dwEventTotal ++;                              // 总数加一

4、在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构,注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDING(I/O操作没有完成)。

[cpp] view plaincopy
  1. if(WSARecv(AcceptSocket ,&DataBuf,1,&dwRecvBytes,&Flags,
  2. & AcceptOverlapped, NULL) == SOCKET_ERROR)
  3. {
  4. // 返回WSA_IO_PENDING是正常情况,表示IO操作正在进行,不能立即完成
  5. // 如果不是WSA_IO_PENDING错误,就大事不好了~~~~~~!!!
  6. if(WSAGetLastError() != WSA_IO_PENDING)   
  7. {
  8. // 那就只能关闭大吉了
  9. closesocket(AcceptSocket);
  10. WSACloseEvent(EventArray[dwEventTotal]);
  11. }
  12. }

5、使用步骤3的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态。

6、WSAWaitForMultipleEvents函数返回后,针对“已传信”状态的事件,调用WSAResultEvent函数来重置事件,从而重设事件对象,并对完成的重叠请求进行处理。

[cpp] view plaincopy
  1. WSAResetEvent(EventArray[dwIndex]);
[cpp] view plaincopy
  1. DWORD dwIndex;
  2. // 等候重叠I/O调用结束
  3. // 因为我们把事件和Overlapped绑定在一起,重叠操作完成后我们会接到事件通知
  4. dwIndex = WSAWaitForMultipleEvents(dwEventTotal,
  5. EventArray ,FALSE ,WSA_INFINITE,FALSE);
  6. // 注意这里返回的Index并非是事件在数组里的Index,而是需要减去WSA_WAIT_EVENT_0
  7. dwIndex = dwIndex – WSA_WAIT_EVENT_0;

7、使用WSAGetOverlappedResult函数来判断重叠调用的返回状态是什么。

[cpp] view plaincopy
  1. DWORD dwBytesTransferred;
  2. WSAGetOverlappedResult( AcceptSocket, AcceptOverlapped ,
  3. &dwBytesTransferred, FALSE, &Flags);
  4. // 先检查通信对方是否已经关闭连接
  5. // 如果==0则表示连接已经,则关闭套接字
  6. if(dwBytesTransferred == 0)
  7. {
  8. closesocket(AcceptSocket);
  9. WSACloseEvent(EventArray[dwIndex]);    // 关闭事件
  10. return;
  11. }

8、在套接字上投递另一个重叠WSARecv请求。

9、重复5到8。

在上文中我们提到一个AcceptEx函数,这个函数是在重叠I/O模型中允许以一种重叠方式,事件对客户端连接。他位于Mswsock.h头文件以及Mswsock.lib库文件中。

这个函数和accept的区别是:我们必须提供接受的套接字,而不是让函数自动为我们创建。

[cpp] view plaincopy
  1. BOOL AcceptEx(
  2. __in          SOCKET sListenSocket,//一个监听套接字
  3. __in          SOCKET sAcceptSocket,//指定另一个套接字,负责对进入连接请求的“接受”
  4. __in          PVOID lpOutputBuffer,//指定一个特殊的缓冲区,因为要负责三种数据的接收,服务器的本地地址,客户端的远程地址和新建连接上发送的第一个数据块。
  5. __in          DWORD dwReceiveDataLength,//以字节为单位,指定在lpOutputBuffer缓冲区中,保留多大的空间来接收数据。  如果传0,那么就不会再接收任何数据了。
  6. __in          DWORD dwLocalAddressLength,//
  7. __in          DWORD dwRemoteAddressLength,//和上一个参数以字节为单位指定在lpOutputBuffer缓冲区中,保留多大空间,在一个套接字被接受的时候,用于本地和远程地址信息的保存
  8. __out         LPDWORD lpdwBytesReceived,//用于返回接收到的实际数据量,以字节为单位。只有在以同步方式完成的前提下,才会设置这个参数。
  9. __in          LPOVERLAPPED lpOverlapped//对应的是一个OVERLAPPED结构,允许AcceptEx以一种异步方式工作。 前面说过,只有在一个重叠I/O应用中,这个函数才需要使用事件对象通知机制。
  10. );

要知道AcceptEx函数只能由这里给大家说的“事件通知”方式获取异步I/O请求的结果,在"完成例程”中是无法使用的。

下面给出win32重叠io,来读取文件:

[cpp] view plaincopy
  1. char buf[512*10000];
  2. int readdata(void)
  3. {
  4. BOOL bRet;   //返回值
  5. HANDLE hFile;   //文件指针
  6. DWORD numRead;   //读取数据长度
  7. OVERLAPPED overlapped;   //I/O结构
  8. hFile = CreateFile("..\\se.zip", GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
  9. FILE_FLAG_OVERLAPPEN, NULL);
  10. if (INVALID_HANDLE_VALUE == hFile)
  11. {
  12. return -1;
  13. }
  14. memset(buf, 0, sizeof(char) * 512*10000);   //初始化缓冲区
  15. memset(&overlapped, 0, sizeof(overlapped));   //初始化overlapped
  16. overlapped.Offset = 0;    //读文件的位置
  17. bRet = ReadFile(hFile, buf, 512*10000, &numRead, &overlapped);
  18. if(TRUE == bRet)  //读取数据成功
  19. {
  20. //读取数据完毕
  21. }
  22. else  //重叠io操作
  23. {
  24. if (ERROR_IO_PENDING == GetLastError())  //读取操作等待中
  25. {
  26. WaitForSingleObject(hFile, INFINITE);   //等待文件句柄被激活
  27. //读取结果
  28. bRet = GetOverlappedResult(hFile, &overlapped, &numRead, TRUE);
  29. if(TRUE == bRet)
  30. {
  31. //读取数据完毕
  32. //处理数据
  33. }
  34. else
  35. {
  36. //处理错误
  37. }
  38. }
  39. else
  40. {
  41. //处理错误
  42. }
  43. }
  44. CloseHandle(hFile);
  45. }
  46. }

下面将讲述完成端口,那个更加高效,但是也更加困难;

2012/9/2

jofranks 于南昌

【网络编程】之十、重叠IO Overlapped IO相关推荐

  1. alin的学习之路(Linux网络编程:十)(http协议,BS模型)

    alin的学习之路(Linux网络编程:十)(http协议,BS模型) 需求:使用B/S模型来访问主机中的文件(包括目录) 0. B/S 模型 注意事项 1. 浏览器请求ico ​ 准备一个favic ...

  2. 【免杀前置课——Windows编程】十四、异步IO——什么是异步IO、API定位问题、APC调用队列

    异步IO 异步IO 异步I/0注意事项: 定位问题 总解决方案 APC调用队列 异步IO 当我们读取一个文件时,一般情况下,线程是阻塞的,也就是说,当前线程在等待文件读取操作结束,这种方式叫同步IO. ...

  3. Win32多线程 -- 异步IO(overlapped IO)

    一. 异步(overlapped) IO之被激发的File Handle 1.1 Win32 IO操作函数 Win32 之中有三个基本的函数用来执行 I/O      CreateFile()    ...

  4. 【网络编程】之十一、重叠IO Overlapped IO 完成例程

    完成例程是Win Sockets提供的另一种管理完成的重叠I/O方法,完成例程是一个函数,当发起重叠操作时,将该函数传递给发起操作的函数,当重叠IO操作完成时由系统调用. 下面来看一下例程必须拥有下面 ...

  5. 网络编程模型综述 之 成熟的IO框架介绍

    ACE "重量级的C++ I/O框架,用面向对象实现了一些I/O策略和其它有用的东西,特别是它的Reactor是用OO方式处理非阻塞I/O,而Proactor是用OO方式处理异步I/O的( ...

  6. 网络编程中同步与异步,IO阻塞与非阻塞总结

    IO操作分两个阶段 第1个阶段:等待数据准备好(从外部设备磁盘或网络读到内核缓冲区): 第2个阶段:采用系统调用(内核进程),操作系统内核将数据从内核缓冲区读到用户空间. 第1阶段花费的时间远远大于第 ...

  7. 串口通讯编程一日通2(Overlapped IO模型)

    第一篇初步了解串口的大致运作,接下来我们看基本操作 先看串口操作的数据结构: 串口操作有几个比较重要的Struct 1.Overlapped I/O 异步I/O模型 异步I/O和同步I/O不同,同步I ...

  8. linux网络编程二十:socket选项:SO_RCVTIMEO和SO_SNDTIMEO

  9. 网络编程-libevnet不带缓冲的事件框架

    网络编程-libevnet不带缓冲的事件框架 1.1工作流程 图1 工作流程 常用的API: 分类 API 含义 事件 框架 struct event_base* event_base_new(voi ...

最新文章

  1. 动物模型:急性肝脏损伤模型的构建及选择
  2. Ubuntu 12.04嵌入式交叉编译环境arm-linux-gcc搭建过程图解
  3. Qt Creator优化移动设备的应用程序
  4. vins中imu融合_VINS-Mono代码分析与总结(最终版)
  5. UML--组件图,部署图
  6. html怎么实现单个li效果,JS+CSS实现的一个li:hover效果
  7. 【clickhouse】clickhouse 分区表
  8. 关系网络理论︱细讲中介中心性(Betweeness Centrality)
  9. java jpa自身关联_java-如何通过JPA / Hibernate加入获取两个关联
  10. 迭代算法与递归算法的概念及区别
  11. Windows系统下hosts文件工作原理(转)
  12. python实现字符串去重
  13. 如何实现验证码输入正确与否的判断?
  14. 接口01_精通Postman接口测试基础应用
  15. ios 持续获取定位 高德地图_【IOS开发】高德地图定位坐标偏差()
  16. Spring 4.x 源码系列4-创建bean实例
  17. 粒子群算法python(含例程代码与详解)
  18. Python源代码保密、加密、混淆
  19. DWORD类型的时间转换
  20. 自制激光雷达(激光扫描)

热门文章

  1. spring FactoryBean的知识应用和Beanfactory的区别
  2. POI文件导入:需求说明
  3. 序列化和反序列化的概述
  4. redis常见应用场景
  5. Java中的查找树和哈希表(一级)
  6. SpringBoot 路径访问控制
  7. php文件上传指定路径,php上传文件到指定文件夹
  8. 专门为ADO二层升三层的咏南中间件(特种用途)
  9. nginx+tomcat的keepalive验证、bio/nio连接比较
  10. yield next和yield* next的区别