我创建了一个socket,使用WSAEventSelect监视FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE等事件,同时我开了一个子线程,不停地用WSAWaitForMultipleEvents和WSAEnumNetworkEvents监视网络事件,我的问题是,现在已经收到有FD_READ事件,但是我怎么知道这个时刻,收到的数据大小有多少?

解决方案 »

  1.   

    WSAEventSelect通常都和OverlappedIO一起使用在你Wait之后,使用WSAGetOverlappedResult(Acceptedsocket,&AcceptOverlapped,&BytesTransfered,FALSE,&Flags);
    来获取已经收到的字节.
    前面提交WSARecv (Acceptedsocket,&DataBuf,1,&RecvBytes,&Flags,&AcceptOverlapped,NULL)
      

  2.   

    BytesTransfered这个就是你收到的字节数,如果和你WSARecv请求的字节数不够,你可以继续提交WSARecv
      

  3.   

    这么说来,只能在收到FD_READ事件的时候这样做了:
    用WSARecv接收一段数据(比如1K大小字节),如果收到的数据未达到这个大小,则不管如何这次FD_READ结束,如果1K的I/O接收完成,则马上再一个while循环继续读下一个1K大小的数据,直到某次读取I/O未全部接收完,在这种情形下退出while,再继续判断下一个FD_READ事件。
    但这样运算量稍大了些,如果可以有直接判断多少个数据在接收缓冲的SOCKET函数,那就在FD_READ的时候一个阻塞的recv函数把这些数据全部读完就可以了。
      

  4.   

    WSAEventSelect本质上是 非阻塞同步模型,重叠i/o是 非阻塞异步模型WSAEventSelect 一般不和 重叠i/o一起使用,而是单独使用;因为他们的事件探测机制不一样,WSAEventSelect使用WSAEnumNetworkEvents来判断具体的发生事件;而重叠i/o使用WSAGetOverlappedResult函数和自己定义的结构来判断是接受还是发送;
      

  5.   

    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达到你协议规定的长度是,就可以解析数据报了
      

  6.   

    问楼上:在非阻塞模式下WSAEventSelect,
    如果想知道
    提交一次WSARecv之后,每次接收到FD_READ事件的时候,每次提交WSARecv的所接收到的字节数,如何处理?我的看法是,假设第一次提交WSARecv,请求接收2048个字节放入程序定义的2048字节buffer,因为是非堵塞模式,接收到FD_READ事件之后,有可能实际接收到了1024个字节,buffer并未填充完整,那么如何在处理这次FD_READ的时候,得到已经接收到的1024个字节。每次读写的时候同时更新  sendbuf和send_len,?接收第一次FD_READ的时候是否能确保接收到的字节数就是你所请求的字节数,此时更新这个长度似乎不大妥当?
      

  7.   

    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也是这样实现的。
      

  8.   

    一般协议是这样定义结构体的 包头(数据包长度,命令字,状态字,序号等字段)+包体(数据)。
       我想你做程序的时候肯定针对每个 socket句柄都定义了一个
       struct my{
        SOCKET s;
        char sendbuf[2048];
        char recvbuf[2048];
        int send_len;
        int recv_len;
       }每次读写的时候同时更新  sendbuf和send_len,当send_len达到你协议规定的长度是,就可以解析数据报了一般都需要这个结构,因为每次读写多少字节都不知道,重要保证一个当前读写指针吧
      

  9.   

    如果只是计算自已的socket流量可以累计send/recv,每秒进行统计
      

  10.   

    to anjuta_c
    受教了,从你的回答里,顺便重温了一下EventSelect模型了解了FD_READ的究竟。还有一个问题需要确认。我先测试之后再来讨论
      

  11.   

    多谢大家对该问题的关注。
    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);
    }
    }