WSAEventSelect模型的socket,怎么在接收数据事件中判断收到的数据数量?

sproll 2007-03-14 10:31:04
我创建了一个socket,使用WSAEventSelect监视FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE等事件,同时我开了一个子线程,不停地用WSAWaitForMultipleEvents和WSAEnumNetworkEvents监视网络事件,我的问题是,现在已经收到有FD_READ事件,但是我怎么知道这个时刻,收到的数据大小有多少?
...全文
908 13 打赏 收藏 举报
写回复
13 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
尘雨 2007-03-15
to anjuta_c
受教了,从你的回答里,顺便重温了一下EventSelect模型了解了FD_READ的究竟。还有一个问题需要确认。我先测试之后再来讨论

  • 打赏
  • 举报
回复
sproll 2007-03-15
多谢大家对该问题的关注。
skywoodsky,这个方案恐怕不能接受。在TCP/UDP包中再次以自己的包头包尾格式封装,CPU计算
量及带宽上的消耗令人无法接受。
anjuta_c,我目前想获取的就是使用WSAEnumNetworkEvents判断网络事件,发生事件后再使用重叠的I/O操作读写数据,但我使用了重叠的I\O模拟阻塞的I\O,见我下面的函数。我测试了之后发现你的看法是正确的:“也就是说WSAEventSelect通知你FD_READ的时候,你的socket缓冲区已经有数据了。你WSARecv一下就可以了”
目前我的操作方式是:使用WSAEventSelect选择FD_READ事件,在该事件发生后使用ioctlsocket得到socket缓冲区中的数据数量 ,然后使用recv或者WSARecv(目前我只测试了WSARecv)读取数据,试验结果,使用WSARecv读取该长度的数据时,返回值显示立即成功,不再是WSA_IO_PENDING,这一点证明anjuta_c所说的,如果重叠I\O读取长度不超过缓冲区中数据的数量,那么该I\O是一次性的阻塞方式。
这是我的部分代码:

//新线程读取网络事件
DWORD WINAPI CTcpClient::ThreadProc(LPVOID lpParameter)
{
CTcpClient* pThis = (CTcpClient*)lpParameter;

WSANETWORKEVENTS event;

while (WaitForSingleObject(pThis->m_hExit, 0) != WAIT_OBJECT_0)
{
if (WSAWaitForMultipleEvents(1, &pThis->m_hEvent, true, 200, false)
== WSA_WAIT_EVENT_0)
{
if (WSAEnumNetworkEvents(pThis->m_socClient, pThis->m_hEvent, &event) == 0)
{
if (pThis->m_pFuncMes != NULL)
{
unsigned long lRead = 0;
if ((event.lNetworkEvents & FD_READ) || (event.lNetworkEvents & FD_WRITE))
{
ioctlsocket(pThis->m_socClient, FIONREAD, &lRead);
pThis->m_pFuncMes(event.lNetworkEvents, pThis->m_pData, &lRead);
}
else
{
pThis->m_pFuncMes(event.lNetworkEvents, pThis->m_pData, NULL);
}
}
}
WSAResetEvent(pThis->m_hEvent);
}
}

return 0;
}
//在发生网络事件后,回调函数pThis->m_pFuncMes里面调用重叠I\O模拟的阻塞I\O方式读取数据,调试结果显示读取立即成功返回,没有WSA_IO_PENDING
int CTcpClient::ReadData(BYTE* pcBuf, int &nLen, WSAOVERLAPPED* pOverlapped)
{
if (pOverlapped == NULL)
{
int nRet = 0;

WSAEVENT Event = WSACreateEvent();
WSAOVERLAPPED Overlapped;
memset(&Overlapped, 0, sizeof(WSAOVERLAPPED));
Overlapped.hEvent = Event;

DWORD dwFlag = 0;

WSABUF buf;
buf.buf = (char*)pcBuf;
buf.len = nLen;

if (WSARecv(m_socClient, &buf, 1, (DWORD*)&nLen, &dwFlag, &Overlapped, NULL)
== SOCKET_ERROR)
{
TRACE("\n%d", WSAGetLastError());
if (WSAGetLastError() == WSA_IO_PENDING)
{
if (WSAWaitForMultipleEvents(1, &Event, true, WSA_INFINITE, false)
== WSA_WAIT_EVENT_0)
{
nRet = 0;
}
else
{
nRet = 1;
}
}
else
{
//CSngTpl<CErrorLog>::Instance()->SetSdkError(WSAGetLastError());
nRet = 1;
}
}
else
{
nRet = 0;
}
WSACloseEvent(Event);
return nRet;
}
else
{
WSABUF buf;
buf.buf = (char*)pcBuf;
buf.len = nLen;

DWORD dwFlag = 0;

return WSARecv(m_socClient, &buf, 1, (DWORD*)&nLen, &dwFlag, pOverlapped, NULL);
}
}
  • 打赏
  • 举报
回复
mynamelj 2007-03-14
如果只是计算自已的socket流量

可以累计send/recv,每秒进行统计
  • 打赏
  • 举报
回复
anjuta_c 2007-03-14
一般协议是这样定义结构体的 包头(数据包长度,命令字,状态字,序号等字段)+包体(数据)。
我想你做程序的时候肯定针对每个 socket句柄都定义了一个
struct my{
SOCKET s;
char sendbuf[2048];
char recvbuf[2048];
int send_len;
int recv_len;
}

每次读写的时候同时更新 sendbuf和send_len,当send_len达到你协议规定的长度是,就可以解析数据报了

一般都需要这个结构,因为每次读写多少字节都不知道,重要保证一个当前读写指针吧
  • 打赏
  • 举报
回复
anjuta_c 2007-03-14
to vieri_ch

首先要弄明白一点 被WSAEventSelect管理的socket句柄是会自动被设置为non-blocking的,不管套接字原来是什么模式
The WSAEventSelect function automatically sets socket s to nonblocking mode, regardless of the value of lNetworkEvents.

1 既然是WSAEventSelect使用i/o模式,要明白由WSAEventSelect管理的socket对象中,如果其中有一个socket对象的缓冲区中已经收到数据,都会激活和socket关联的event object,并发生FD_READ事件;然后进程可以调用recv或者WSARecv函数收取数据,注意这个时候发生的是同步i/o,也就是说:recv或者WSARecv不把socket缓冲区的数据拷贝到进程自己的缓冲区,是不会返回的。收到的字节数是recv的返回值或者是WSARecv的第4个参数。
楼上模糊了一个概念,是收到fd_read再recv;而不是先recv,然后再收到fd_read;这个模型基本上和select模型一致


2 如果使用overlapped i/o模型,则创建socket的时候需要指定overlapped标志。这种模式下才是先WSARecv(提交i/o请求),WSARecv立即返回WSAEWOULDBLOCK;然后才在用户自己的线程中调用WSAGetOverlappedResult来获得已经提交i/o的结果。这是个 非阻塞异步i/o;包括iocp也是这样实现的。
  • 打赏
  • 举报
回复
sproll 2007-03-14
这么说来,只能在收到FD_READ事件的时候这样做了:
用WSARecv接收一段数据(比如1K大小字节),如果收到的数据未达到这个大小,则不管如何这次FD_READ结束,如果1K的I/O接收完成,则马上再一个while循环继续读下一个1K大小的数据,直到某次读取I/O未全部接收完,在这种情形下退出while,再继续判断下一个FD_READ事件。
但这样运算量稍大了些,如果可以有直接判断多少个数据在接收缓冲的SOCKET函数,那就在FD_READ的时候一个阻塞的recv函数把这些数据全部读完就可以了。
  • 打赏
  • 举报
回复
skywoodsky 2007-03-14
如果不是大量连接,干嘛要去用重叠模型
长度不定就多读几次,自然可以用返回判断
  • 打赏
  • 举报
回复
尘雨 2007-03-14
BytesTransfered这个就是你收到的字节数,如果和你WSARecv请求的字节数不够,你可以继续提交WSARecv
  • 打赏
  • 举报
回复
尘雨 2007-03-14
WSAEventSelect通常都和OverlappedIO一起使用

在你Wait之后,使用WSAGetOverlappedResult(Acceptedsocket,&AcceptOverlapped,&BytesTransfered,FALSE,&Flags);
来获取已经收到的字节.
前面提交WSARecv (Acceptedsocket,&DataBuf,1,&RecvBytes,&Flags,&AcceptOverlapped,NULL)
  • 打赏
  • 举报
回复
尘雨 2007-03-14
问楼上:

在非阻塞模式下WSAEventSelect,
如果想知道
提交一次WSARecv之后,每次接收到FD_READ事件的时候,每次提交WSARecv的所接收到的字节数,如何处理?

我的看法是,假设第一次提交WSARecv,请求接收2048个字节放入程序定义的2048字节buffer,因为是非堵塞模式,接收到FD_READ事件之后,有可能实际接收到了1024个字节,buffer并未填充完整,那么如何在处理这次FD_READ的时候,得到已经接收到的1024个字节。

每次读写的时候同时更新 sendbuf和send_len,?接收第一次FD_READ的时候是否能确保接收到的字节数就是你所请求的字节数,此时更新这个长度似乎不大妥当?

  • 打赏
  • 举报
回复
anjuta_c 2007-03-14
to lz()
如果可以有直接判断多少个数据在接收缓冲的SOCKET函数,那就在FD_READ的时候一个阻塞的recv函数把这些数据全部读完就可以了。

这句话说得不对,因为使用WSAEventSelec i/o模型,所以recv函数肯定是非阻塞的,也就是说WSAEventSelect通知你FD_READ的时候,你的socket缓冲区已经有数据了。你WSARecv一下就可以了,不要在FD_READ下边循环读,那样是busy loop;应该通知一下读一下。

一般协议是这样定义结构体的 包头(数据包长度,命令字,状态字,序号等字段)+包体(数据)。
我想你做程序的时候肯定针对每个 socket句柄都定义了一个
struct my{
SOCKET s;
char sendbuf[2048];
char recvbuf[2048];
int send_len;
int recv_len;
}

每次读写的时候同时更新 sendbuf和send_len,当send_len达到你协议规定的长度是,就可以解析数据报了
  • 打赏
  • 举报
回复
anjuta_c 2007-03-14
WSAEventSelect本质上是 非阻塞同步模型,重叠i/o是 非阻塞异步模型

WSAEventSelect 一般不和 重叠i/o一起使用,而是单独使用;因为他们的事件探测机制不一样,WSAEventSelect使用WSAEnumNetworkEvents来判断具体的发生事件;而重叠i/o使用WSAGetOverlappedResult函数和自己定义的结构来判断是接受还是发送;
  • 打赏
  • 举报
回复
skywoodsky 2007-03-14
貌似没有办法直接得到大小得
要么你在包头说明长度
要么就是按你说得那样作
  • 打赏
  • 举报
回复
发帖
网络编程

1.8w+

社区成员

VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
帖子事件
创建了帖子
2007-03-14 10:31
社区公告
暂无公告