WSAEventSelect模型是个非阻塞模型。在学习这个模型的时候发现示例代码并未为每一个连接建立FD_WRITE事件监视,只是监视了RD_READ/FD_CLOSE事件。而是直接这么使用了send操作:
/*
 * 发送数据
 */
BOOL CClientSocket::Send(const u_short type, const CString &strData)
{
ASSERT(!strData.IsEmpty());

int nErrCode; //返回值
PACKETHDR packetHdr; //定义包头 packetHdr.type = type; //类型
packetHdr.len = strData.GetLength();//数据长度 //发送包头
nErrCode = send(m_s, (char*)&packetHdr, PACKETHDRLEN, 0);
if (SOCKET_ERROR == nErrCode)
{
AfxMessageBox("发送用户列表错误!");
return FALSE;
} //发送包体
nErrCode = send(m_s, strData, packetHdr.len, 0);
if (SOCKET_ERROR == nErrCode)
{
AfxMessageBox("发送用户列表错误!");
return FALSE;
} return TRUE;
}这里我有些不解,这能够保证send()函数操作一次性完成么?如果万一发生WSAEWOULDBLOCK错误怎么办?这个模型本来就是非阻塞异步模型。这里的用法是不是有些问题?应该如何使用呢?

解决方案 »

  1.   

    貌似你的这个Send函数的返回值不对吧,继承自CAsyncSocket的Send的返回值是int类型,就算不是继承自CAsyncSocket,那么你写的这个Send函数也应该返回int类型,或者在函数内判断是否发送完毕,不能直接返回true/false了事
      

  2.   

    这个是API实现,没有使用MFC的socket接口类。
      

  3.   

    判断一下send的返回值,和实际发送字节数的是否一致,如果小于后者,就循环发送后续的数据
      

  4.   

    这是一个介绍WSAEventSelect模型的例子程序,我就是觉得这个函数的编写以及对send操作的设计有问题,才来求教的。本来这个模型里面可以对FD_WRITE事件进行注册的,这个例子并没有注册这个事件,而是一次性的发送数据Send()。我考虑由于他是非阻塞模型,所以这个Send()函数内的socket send()函数未必能够一次性执行成功,很可能返回SOCKET_ERROR并且属于WSAEWOULDBOCK类型的错误。那么就需要判断是否正确的发送,并且对异常进行处理。但是这里并没有做合理的实现,我也很不解这个模型的实际使用方法。
      

  5.   

    并不是有的代码都是对的,你最好还是先看看有关socket的文章,在来看代码,发现代码不对,在反过去看文章,这样就会记住
      

  6.   

    还有,FD_WRITE事件注册是用来提示你发送缓冲区为空,可以继续发送数据了,这和调用send是两码事,不能因为注册了FD_WRITE就不去调send了,那你的数据将永远不会被发送
      

  7.   

    其实再来看看它设计的Recv函数的实现,同样发现一些问题不能理解疑问:
    函数开始为了避免多次接收recv()函数触发的FD_READ事件,清除了监测事件列表,但是并没有再所有的return分支,重新注册事件FD_READ/FD_CLOSE。比如下面的两个分支,虽然是异常分支,但是程序并没有终止运行,事件有可能二次触发,那么这里取消过之后没有重新监视,也就说异常发生后永远不可能二次捕获事件了,监视列表已经为空。if ( 0 == reVal)
    {
    return FALSE;
    }
    else if (SOCKET_ERROR == reVal)//网络错误
    {
    int nErrCode = WSAGetLastError();
    if (WSAEWOULDBLOCK == nErrCode)
    {
    return TRUE;
    }
    AfxMessageBox("接收用户列表错误!");
    return FALSE;
    }/*
     * 接收数据
     */
    BOOL CClientSocket::Recv( void )
    {
    int reVal; //返回值 //首先取消网络事件
    WSAEventSelect(m_s, m_hEvent, 0); //获取数据包体的长度
    PACKETHDR packetHdr;
    reVal = recv(m_s, (char*)&packetHdr, PACKETHDRLEN, 0);

    if ( 0 == reVal)
    {
    return FALSE;
    }
    else if (SOCKET_ERROR == reVal)//网络错误
    {
    int nErrCode = WSAGetLastError();
    if (WSAEWOULDBLOCK == nErrCode)
    {
    return TRUE;
    }
    AfxMessageBox("接收用户列表错误!");
    return FALSE;
    }
    else
    { // 处理接收到的数据
    CString strUserInfo; //用户信息
    int nTotalLen = 0; //已经读取字符数量
    int nDataLen = packetHdr.len; //数据长度
    int nReadLen = 0; //每次读取字符数量 
    while ( nTotalLen != nDataLen )
    {
    char cRecv; //接收字符
    nReadLen = recv(m_s, &cRecv, 1,0); //每次接收一个字符
    if (SOCKET_ERROR == nReadLen) //网络错误
    {
    if (WSAEWOULDBLOCK == WSAGetLastError())
    {
    continue;
    }
    AfxMessageBox(_T("读取客户端数据失败!"));
    reVal = FALSE;
    }else if (0 == nReadLen)
    {
    AfxMessageBox(_T("客户端关闭了连接!"));
    reVal = FALSE;
    }else if (nReadLen > 0)
    {
    if ('<' == cRecv) //开始字符
    {
    strUserInfo.Empty();

    }else if ('>' == cRecv) //结束字符
    {
    break;
    }else
    {
    strUserInfo += cRecv; //添加字符
    }
    nTotalLen += nReadLen;
    }
    } //重新注册网络事件
    WSAEventSelect(m_s, m_hEvent, FD_READ|FD_CLOSE); //更新用户列表
    return (m_pServDlg->UpdateUserList(strUserInfo, this));
    }

    return TRUE;
    }
      

  8.   

    这个我理解。FD_WRITE事件的触发和send()无关,而是和系统buffer有关系。connect()后会触发
    FD_WRITE,或者send()遇到WSAEWOULDBLOCK后,系统buffer又可用send()可再次发送时候会触发FD_WRITE事件。但是,不可忽视的是send()函数操作不能确认一定成功,send()是可能导致FD_WRITE事件发生的。
      

  9.   

    我的这个例子程序来自《精通windows sockets网络开发 - 基于visual c++实现》 人民邮电出版社出版。基本上就是《windows网络编程》的翻版。
      

  10.   

    FD_WRITE事件发生,只有在3中条件下,FD_WRITE通知才会发生:
    1。使用connect或者WSAConnect一个套接字首次建立了连接
    2。使用accept或者WSAAccept套接字被接受以后
    3。若send,WSASend,sento或者WSASendTo操作失败,返回了WSAEWOULDBLOCK错误,而且缓冲区的空间变得可用时
      

  11.   

    是的,这个是FD_WRITE在WSAEventSelect和WSASyncSelect模型的事件触发定义。我这个例子的Send/Recv函数的设计我怎么都觉得有些错误隐藏在里面,这个作者也太垃圾了吧。看的我头大,还以为自己的理解出现问题了呢。人民邮电出版社,孙海民编著的一本书。