关于重叠IO(overlapped)模型中完成例程使用的两点疑问

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于重叠IO(overlapped)模型中完成例程使用的两点疑问相关的知识,希望对你有一定的参考价值。

问题1:在完成例程的回调函数CALLBACK CompletionRoutine 内,如何通过回调参数 Overlapped 来判断是哪个套接字发生了IO操作?我看了几个例子,好像通常是自己定义一个结构体然后把WSAOVERLAPPDE放在第一的位置,然后在回调函数内强制转换,比如:
C/C++ code
// 这里是定义的结构体typedef struct _SOCKET_INFORMATION

WSAOVERLAPPED Overlapped; // 重叠结构 完成例程中此结构一定要放在最前面 CHAR cpIP[16];
CHAR Buffer[DATA_BUFSIZE];
WSABUF DataBuf;
SOCKET s;
SOCKET_INFORMATION,*LPSOCKET_INFORMATION;

// CALLBACK CompletionRoutine 的第一行进行强制转换LPSOCKET_INFORMATION SI = (LPSOCKET_INFORMATION)Overlapped;

我不理解的是:为什么在回调函数内这样强制转化就能够得到是哪个套接字发生了IO操作呢?请大家帮我理解一下这里的指针操作!谢谢!

问题2:接上述问题,我是将自己定义的这个结构体换成了一个自己的用户类,CXEDUser,类里也定义了WSAOVERLAPPED结构体,而且也如结构体一样,定义了成员变量sock, flags, dwRecvBytes,等等。并且也把overlapped放到成员变量的第一位,然后用类的指针,按照结构体的方法强制转换,但是得到了一个颠倒的结果,overlapped变成了dwRecvBytes, dwRecvBytes变成了sock,也就是所取到的值和变量的名称不是对应的了,是错乱的,估计是指针进行强制转化的时候出了问题,请问要如何操作才能像结构体那样成功转化呢?

代码:
C/C++ code
// CXEDUser 类的头文件class CXEDUser

public:
WSAOVERLAPPED m_Overlapped;
UINT m_uPort;
LPCTSTR m_lpIPAddress;
SOCKET m_UserSock;
WSAEVENT m_Event;
WSABUF m_DataBuf;
DWORD m_dwRecvBytes, m_dwFlags;
CXEDUser(SOCKET UserSock, LPCTSTR lpIPAddress, UINT uPort);
CXEDUser();
virtual~CXEDUser();

;

// 在回调函数中的强制转化 CXEDUser* pUser = (CXEDUser*)Overlapped;
TRACE("%d\n", pUser->m_Overlapped);// 这里输出的结果变成了发生IO操作的字节数,也就是dwRecvBytes

非常感谢
知道真是的,只能把分给一个人,其实几位的回答对我都有很大的帮助!真心的感谢各位!

对于你提出的问题有点复杂
问题1: 为什么在回调函数内这样强制转化就能够得到是哪个套接字发生了IO操作呢
是这样的,在你调用WriteFileEx或者ReadFileEx时,第4个参数就是你定义的OVERLAPPED为第一个元素的结构的指针,当IO操作完成后,系统会自动调用你所定义的FileIOCompletionRoutine,并将你调用WriteFileEx时的第4个参数的传递给FileIOCompletionRoutine的第3个参数,这个过程中传递的只是指针(即结构的地址),所以你在结构后面定义多少你自己的内容,系统都会帮你传递过去,例如结构里面包含套接字消息,那么系统帮你传递过去之后,你当然就可以知道是哪个套接字发生的IO

问题2:为什么前面定义的结构可以,但转化为类就不行了
这是因为WriteFileEx或者ReadFileEx在执行重叠IO操作时,对第4个参数,它不会去理你是什么数据类型,只需要你所给出的指针所指地址的前几个数据为OVERLAPPED的结构就可以了,也就是说WriteFileEx或者ReadFileEx的第4个参数所指的地址的内容必须是以OVERLAPPED开头的数据结构。
现在再说说类的数据结构,只要你的类的内部除了定义成员变量外还包含有成员函数(包括构建函数和析构函数),那么这个类就会自动生成一个叫做虚函数表指针的一个东西,而且是放在类结构的最前面,此时即使你将OVERLAPPED的结构放在类的最前面定义,那么这个类的指针所指地址的数据结构依然是:最前面0-3字节为虚函数表指针,从第4字节开始才是你所定义成员变量的地址,这样的地址传递给WriteFileEx或者ReadFileEx时,并不符合WriteFileEx或者ReadFileEx对第4个参数的要求(以OVERLAPPED开头),因为此时是以虚函数表指针开头的,所以即使你做了强制类型转化,执行起来也是不对的.

啰啰嗦嗦这么多,不知道有没有将问题说清楚.若仍有问题,欢迎HI我,我们一起研究追问

大师留个扣扣吧~期待交流

追答

我的QQ : 874150805

参考技术A 1. 也不一定要放在第一个位置, 放在自定成员的任何位置都可以. 只是放在第一个位置计算上方便些.
因为是第一个成员, 这个成员的地址, 也就是这个结构变量的地址.
2. 你自己的类继承与OVERLAPPED结构就可以了.追问

1. 我是不理解为什么强制转化后就能得有IO操作的那个套接字。能说明一下原理吗?
2.用自己的类来继承一个结构体吗?

追答

1. 比如自己的结构为:
typedef struct _BLOCK

OVERLAPPED over;
SOCKET socket;
WSABUF buf;
BLOCK_DATA, *PBLOCK_DATA;
你要理解这个结构体成员在内存的布局. 那你就明白了. 可以在调试时, 把结构体变量地址放到内存窗口中看看.

2. 类可以继承于结构的. 比如MFC库的CRect/CPoint/CSize类, 是从标准系统结构继承的.
比如使用类为: class CBlock : public OVERLAPED;

传进去时把类转为OVERLAPPED指针即可. 收到时也转为CBlock类指针.

参考技术B 字节数一样转换是最好的,字节数不等转换就会有问题
CXEDUser和Overlapped的数据成员都不一样,你转换我都不知道变成什么了

windows的重叠IO模型

windows的重叠IO模型

2019529

11:58

? ?

同一线程内部向多个目标传输(或冲多个目标接收)数据引起的IO重叠现象称为"重叠IO"。为了完成这项任务,调用IO的函数应立即返回,只有这样才能返送后续数据。

重叠IO收发数据最重要的前提条件就是异步IO。

在windows中重叠IO的重点并非IO本身,而是如何确认IO完成时的状态。因为不管输入还是输出,只要时非阻塞模式的,就要另外确认执行结果。确认执行结果前需要经过特殊的处理过程。

? ?

创建重叠IO套接字

#include <winsock2.h>

SOCKET WSASocket(int af, int type, int protocal, LPWSAPROTOCAL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags);

->>成功时返回套接字句柄,失败时返回INVALID_SOCKET

? ?

af 协议族信息

type 套接字数据传输方式

protocol 套接字使用协议信息

lpProtocalInfo 包含创建的套接字信息的WSAPROTOCAL_INFO结构体变量地址值,不需要时传递NULL

g 为扩展函数二预约的参数,可以使用0

dwFlags 套接字属性信息

? ?

首先是创建重叠IO套接字,并且之后要使用特殊的重叠IO函数收发数据,并且在收发数据以后通过特殊方法确认收发数据的成功与否。如果需要还可以通过注册在IO完成以后执行指定的函数。

? ?

执行重叠IOWSASend函数

#include <winsock2.h>

int WSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,

LPDWORD lpNumberOfBytesSent, DWORD dwFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);

->>成功时返回0,失败时返回SOCKET_ERROR

? ?

s 套接字句柄,传递具有重叠IO属性的套接字句柄时,以重叠IO模型输出

lpBuffers WSABUF结构体变量数组的地址值,WSABUF中存有待传输的数据

dwBufferCount 第二个参数中数组的长度

lpNumberOfBytesSent 用于保存实际发送字节数的变量地址

dwFlags 用于更改数据传输特性

lpOverlapped WSAOVERLAPPED结构体变量的地址值,使用事件对象,用于确认完成数据传输

lpCompletionRoutine 传入Completion Routine函数的入口地址值,可以通过干函数确认是否完成数据传输

? ?

WSABUF结构体定义

typedef struct __WSABUF

{

u_long len; // 待传输数据的大小

char FAR * buf; // 缓冲地址值

}WSABUF, * LPWSABUF;

? ?

示例:

WSAEVENT event;

WSAOVERLAPPED overlapped;

WSABUF databuf;

char buf[BUF_SIZE] = {"待传输的数据"};

int recvBytes = 0;

event = WSACreateEvent();

memset(&overlapped, 0, sizeof(overlapped)); // 所有位初始化为0

overlapped.hEvent = event;

dataBuf.len = sizeof(buf);

dataBuf.buf = buf;

WSASend(hSocket, &dataBuf, 1, &recvBytes, 0, &overlapped, NULL);

? ?

WSAOVERLAPPEd 结构体定义如下

typedef struct _WSAOVERLAPPE

{

DWORD Internal;

DWORD InternalHigh;

DWORD Offset;

DWORD OffsetHigh;

WSAEVENT hEvent;

}WSAOVERLAPPED, * LPWSAOVERLAPPED;

Internal, InternalHigh为进行重叠IO时操作系统内部使用的成员,Offset, OffsetHigh同样具有特殊用途,实际只需要关注hEvent成员

? ?

如果向overlapped参数传递NUll,则套接字将以阻塞方式工作。同样如果利用WSASend函数向多个目标传输数据,则每个目标都需要具有独自的WSAOVERLAPPED结构体变量

? ?

还具有一种情况就是在WSASend调用结束立即返回的同时,数据也同时传输完成。在这个时候WSASend函数返回0,同时在recvBytes中填充发送的字节数。反之(数据没有传输完成),WSASend仍需传输数据时,将返回SOCKET_ERROR,并将WSA_IO_PENDING(尚未完成状态Pending)注册为错误代码(错误代码通过WSAGetLastError获得)。之后通过WSAGetOverlappedResult获取实际传输大小。

? ?

执行重叠IOWSARecv函数

#include <winsock2.h>

int WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,

LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);

->>成功时返回0,失败时返回SOCKET_ERROR

? ?

s 赋予重叠IO属性的套接字句柄

lpBuffers 用于保存接收数据的WSABUF结构体数组地址值

dwBufferCount 向第二个参数传递的数组的长度

lpNumberOfBytesRecvd 保存接收数据大小信息的地址值

lpFlags 用于设置或读取传输特性信息

lpOverlapped WSAOVERLAPPED结构体变量地址

lpCompletionRoutine Completion Routine函数地址

? ?

Gather/Scatter I/O是指将多个缓冲区中的数据积累到一定程度后一次型传输(Gather输出),将接收的数据分批保存(Scatter输入)。Linux下的writev&readv函数具有Gather/Scatter I/O功能,但是windows下并没有这些函数的定义。不过可以通过重叠IO中的WSASend和WSARecv函数获得类似的功能。他们的第二个参数和第三个参数中就可以判断出其具有Gather/Scatter I/O功能。

? ?

获得实际传输大小

#include <winsock2.h>

BOOL WSAGetOverlappedResult(SOCKET s, LPWSAOVERLAPPED lpOverlapped,

LPDWORD lpcbTreansfer, BOOL fWait, LPDWORD lpdwFlags);

->>成功时返回TRUE,失败时返回FALSE

? ?

s 重叠IO套接字

lpOverlapped WSAOVERLAPPED结构体变量地址

lpcbTransfer 用于保存实际传输字节数的变量地址

fWait 如果调用该函数时仍在进行IO,是否等待IO完成,TRUE等待,FALSE不等待(返回FALSE并跳出函数)。

lpdwFlags 用于获取附加信息(如OOB消息)。如果不需要,可以传递NULL

? ?

重叠IOIO完成确认

重叠IO中有两种方法确认IO的完成并获取结果

  • 利用WSASend、WSARecv函数的第六个参数,基于事件对象
  • 利用WSASend、WSARecv函数的第七个参数,基于Completion Routine

? ?

使用前一种方法时需要注意:

  • 完成IO时,WSAOVERLAPPED结构体变量引用的事件对象将编程为signaled状态
  • 为了验证IO的完成和完成结构,需要调用WSAGetOverlappedResult函数

? ?

使用Completion Routine函数

可以通过WSARecv&WSASend函数的最后一个参数中指定的Completion Routine(以下简称CR)函数验证IO完成情况。

? ?

"注册CR"有如下含义:"Pending的IO完成以后调用此函数"

但是如果执行重要任务时突然调用Completion Routine,则有可能会破坏程序的正常执行流。因此操作系统通常都会预先定义规则:"只有请求I/O的线程处于alertalble wait状态时才能调用Completion Routine函数"

? ?

"alertable wait状态"时等待接收操作系统消息的线程状态,调用以下函数可以进入alertable wait状态

  • WaitForSingleObjectEx
  • WaitForMultipleObjectsEx
  • WSAWaitForMultipleEvents
  • SleepEx

? ?

第一,第二,第四个参数提供的功能与WaitForSingleObject,WaitForMultipleObjects,Sleep函数相同,只是额外增加了1个参数。如果该参数为TRUE,则相应线程进入alertable wait状态

第三个函数的最后一个参数设置为TRUE时,线程同样进入alertable wait状态。

? ?

启动IO任务后,执行完紧急任务时可以调用上述任意函数验证IO完成与否,此时操作系统知道线程进入alertable wait状态,如果有已经完成的IO。则调用相应的Completion Routine函数。调用以后上述函数将全部返回WAIT_IO_COMPLETION,并开始执行接下来的程序

? ?

Completion Routine函数原型

void CALLBACK CompletionROUTINE(DWORD dwError, DWORD cbTransferred,

LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);

? ?

参数一 中写入错误信息(正常结束时写入0)

参数二 写入实际收发的字节数

参数三 写入WSASend,WSARecv函数的参数lpOverlapped

dwFlags 写入调用IO函数时传入的特性信息或者0

? ?

? ?

??

以上是关于关于重叠IO(overlapped)模型中完成例程使用的两点疑问的主要内容,如果未能解决你的问题,请参考以下文章

WINSOCK.06.重叠IO模型:完成例程

Overlapped Model

四.Windows I/O模型之重叠IO(overlapped)模型

IO 完成端口和重叠管理

Socket编程模型之完成端口模型

关于windows iocp的一个问题