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

XLEdoo 2012-08-08 10:31:56
问题1:在完成例程的回调函数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




非常感谢
...全文
183 5 打赏 收藏 转发到动态 举报
写回复
用AI写文章
5 条回复
切换为时间正序
请发表友善的回复…
发表回复
XLEdoo 2012-08-10
  • 打赏
  • 举报
回复
实在是大神级别的回答~~大神直接指出了我的问题,因为是自学,而且是初学,所有很多基础概念比较模糊,对指针、内存、堆栈的理解都很片面,甚至有很多错误~~~基础没打好,写东西都是边学边BAIDU,很艰难啊~!

不知大神能在百忙中留个QQ给于指点么?
希望大神给于帮助!!!

上分了!
youngwolf 2012-08-09
  • 打赏
  • 举报
回复
虚表肯定要出现在最前面的位置,所以你写到后面也用。

如果非要用虚表,那么在把WSAOVERLAPPED*强转为CUser*的时候,要如下处理:

CUser* pUser = (CUser*) ((UINT_PTR) lop - sizeof(UINP_PTR));
这个公式同样可以用于你上面的CXEDUser,保持CXEDUser不变,在回调函数里面加上面的代码。
youngwolf 2012-08-09
  • 打赏
  • 举报
回复
上面有点问题,继承也不能存在虚表。
youngwolf 2012-08-09
  • 打赏
  • 举报
回复
问题一:
你调用一个函数,比如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; }

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧