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

本文探讨了在网络编程中,如何利用WSAOVERLAPPED结构体和自定义类来识别触发IO操作的套接字。通过具体实例,解析了指针操作和类型转换的原理,以及类成员布局对强制转换的影响。

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

// 这里是定义的结构体
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,也就是所取到的值和变量的名称不是对应的了,是错乱的,估计是指针进行强制转化的时候出了问题,请问要如何操作才能像结构体那样成功转化呢?

// 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

==================================================================
问题一:
你调用一个函数,比如WSARecv,其需要的是一个WSAOVERLAPPED指针,在回调函数里,你得到的也是这个WSAOVERLAPPED指针(它们指向同一片内存),现在比如你想得到cpIP,则它就在从WSAOVERLAPPED指针开始的,sizeof(WSAOVERLAPPED)之后的位置上。而把WSAOVERLAPPED指针强转为_SOCKET_INFORMATION指针,是为了书写上的方法,直接->cpIP就取到了,不强转也是做得到的,就麻烦了,你还需要自己去在内存中移动指针。
相反,如果WSARecv需要一个WSAOVERLAPPED参数,则它就是按值传递的,那么在回调函数里,就无法通过强转得到_SOCKET_INFORMATION,因为传值的时候,只传了WSAOVERLAPPED,后面的_SOCKET_INFORMATION多出来的部分并没有拷贝。
这说白了就是传指针、传引用与传值的区别了,你的c++知识严重不足。

问题二:
因为你有一个~CXEDUser()虚函数,造成了类大小的改变(增加了虚表),那么CXEDUser的地址就不再与成员m_Overlapped地址重合,那么强转就是错误的。
你可以把virtual去掉,应该就可以了(但去掉析构函数的virtual并不标准,不推荐)。

建议:
从WSAOVERLAPPED继承才是最好的解决办法,比那个struct强,你自己慢慢领会:
class CXEDUser : public WSAOVERLAPPED
{
public:
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* user = new(…);
WSARecv(…, user);

回调时:
CXEDUser* user = (CXEDUser*) par;

重叠IO模型OverLapped完成例程模型WSACompletionRoutineServer VS2010 基础入门 客户端与服务器端 客户端向服务器端发送数据 可接收多个客户端 #include #include #pragma comment (lib, "ws2_32.lib") #define PORT 8088 #define MSG_SIZE 1024 SOCKET g_sConnect; bool g_bConnect = false; typedef struct { WSAOVERLAPPED overLap; WSABUF wsaBuf; char chMsg[MSG_SIZE]; DWORD nRecvNum; DWORD nFlags; SOCKET sClient; }PRE_IO_OPERATION_DATA, *LP_PER_IO_OPERATION_DATA; void CALLBACK CompletionRoutine(DWORD dwError, DWORD dwTrans, LPWSAOVERLAPPED lpOverlap, DWORD nFlags); DWORD WINAPI workThread(LPVOID lp) { LP_PER_IO_OPERATION_DATA lpData; while(TRUE) { if (g_bConnect) // 有新的连接 { // 为lpData分配空间并初始化 lpData = (LP_PER_IO_OPERATION_DATA)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PRE_IO_OPERATION_DATA)); lpData->wsaBuf.len = MSG_SIZE; lpData->wsaBuf.buf = lpData->chMsg; lpData->sClient = g_sConnect; WSARecv(lpData->sClient, &lpData->wsaBuf, 1, &lpData->nRecvNum, &lpData->nFlags, &lpData->overLap, CompletionRoutine); g_bConnect = false; // 处理完毕 } SleepEx(1000, TRUE); } return 0; } // 系统在WSARecv收到信息后,自动调用此函数,并传入参数--回调函数 void CALLBACK CompletionRoutine(DWORD dwError, DWORD dwTrans, LPWSAOVERLAPPED lpOverlap, DWORD nFlags) { LP_PER_IO_OPERATION_DATA lpData = (LP_PER_IO_OPERATION_DATA)lpOverlap; if (0 != dwError) // 接收失败 { printf("Socket %d Close!\n", lpData->sClient); closesocket(lpData->sClient); HeapFree(GetProcessHeap(), 0, lpData); } else // 接收成功 { lpData->chMsg[dwTrans] = '\0'; send(lpData->sClient, lpData->chMsg, dwTrans, 0); printf("Socket:%d MSG: %s \n", lpData->sClient, lpData->chMsg); memset(&lpData->overLap, 0, sizeof(WSAOVERLAPPED)); lpData->wsaBuf.len = MSG_SIZE; lpData->wsaBuf.buf = lpData->chMsg; // 继续接收来自客户端的数据 实现 WSARecv与CompletionRoutine循环 WSARecv(lpData->sClient, &lpData->wsaBuf,1, &lpData->nRecvNum, &lpData->nFlags, &lpData->overLap, CompletionRoutine); } } int main() { WSADATA wsaData; WSAStartup(0x0202, &wsaData); SOCKET sListen; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in addrListen; addrListen.sin_family = AF_INET; addrListen.sin_port = htons(PORT); addrListen.sin_addr.S_un.S_addr = htonl(ADDR_ANY); int nErrorCode = 0; nErrorCode = bind(sListen, (sockaddr*)&addrListen, sizeof(sockaddr)); nErrorCode = listen(sListen, 5); DWORD nThreadID; CreateThread(NULL, 0, workThread, NULL, 0, &nThreadID); sockaddr_in addrConnect; int nAddrLen = sizeof(sockaddr_in); printf("Server Started!\n"); while(TRUE) { g_sConnect= accept(sListen, (sockaddr*)&addrConnect, &nAddrLen); if (INVALID_SOCKET == g_sConnect) { return -1; } g_bConnect = true; // 连接成功 printf("Accept Client :%s -- PORT:%d\n", inet_ntoa(addrConnect.sin_addr), htons(addrConnect.sin_port)); } return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值