【问题标题】:Winsock nonblocking send() wait buffer. What is the correct method?Winsock 非阻塞 send() 等待缓冲区。什么是正确的方法?
【发布时间】:2012-06-13 22:24:50
【问题描述】:

我有一些关于何时需要将数据存储在等待缓冲区(等待 FD_WRITE 事件)的问题。

这是我的发送功能(固定):

bool MyClass::DataSend(char *buf, int len)
{
    if (len <=0 || m_Socket == INVALID_SOCKET) return false;

    if (m_SendBufferLen > 0)
    {
        if ((m_SendBufferLen + len) < MAX_BUFF_SIZE)
        {
            memcpy((m_SendBuffer + m_SendBufferLen), buf, len);
            m_SendBufferLen += len;
            return true;
        }
        else
        {
            // close the connection and log insuficient wait buffer size
            return false;
        }
    }

    int iResult;
    int nPosition = 0;
    int nLeft = len;

    while (true)
    {
        iResult = send(m_Socket, (char*)(buf + nPosition), nLeft, 0);

        if (iResult != SOCKET_ERROR)
        {
            if (iResult > 0)
            {
                nPosition   += iResult;
                nLeft       -= iResult;

            }
            else
            {
                // log 0 bytes sent
                break;
            }
        }
        else
        {
            if (WSAGetLastError() == WSAEWOULDBLOCK)
            {
                if ((m_SendBufferLen + nLeft) < MAX_BUFF_SIZE)
                {
                    // log data copied to the wait buffer
                    memcpy((m_SendBuffer + m_SendBufferLen), (buf + nPosition), nLeft);
                    m_SendBufferLen += nLeft;
                    return true;
                }
                else
                {
                    // close the connection and log insuficient wait buffer size
                    return false;
                }
            }
            else
            {
                // close the connection and log winsock error
                return false;
            }
        }

        if (nLeft <= 0) break;
    }

    return true;
}

我的发送(FD_WRITE 事件)函数(已修复):

bool MyClass::DataSendEvent()
{
    if (m_SendBufferLen < 1) return true;

    int iResult;
    int nPosition = 0;
    int nLeft = m_SendBufferLen;

    while (true)
    {
        iResult = send(m_Socket, (char*)(m_SendBuffer + nPosition), nLeft, 0);

        if (iResult != SOCKET_ERROR)
        {
            if (iResult > 0)
            {
                nPosition   += iResult;
                nLeft       -= iResult;
            }
            else
            {
                // log 0 bytes sent
                break;
            }
        }
        else
        {
            if (WSAGetLastError() == WSAEWOULDBLOCK)
            {
                if (nPosition > 0)
                {
                    memmove(m_SendBuffer, (m_SendBuffer + nPosition), (m_SendBufferLen - nPosition));
                    m_SendBufferLen -= nPosition;
                }

                break;
            }
            else
            {
                // close the connection and log winsock error
                return false;
            }
        }

        if (nLeft <= 0)
        {
            if (m_SendBufferLen == nPosition)
            {
                m_SendBufferLen = 0;
                break;
            }
            else
            {
                memmove(m_SendBuffer, (m_SendBuffer + nPosition), (m_SendBufferLen - nPosition));
                m_SendBufferLen -= nPosition;
                nPosition   = 0;
                nLeft       = m_SendBufferLen;
            }
        }
    }

    return true;
}

我真的需要if (nPosition &gt; 0) 吗?如何模拟这种情况?非阻塞模式下的 send() 发送的字节数是否可能少于请求的字节数?如果不是,为什么要使用 while() 循环?

  • 这是最终代码(感谢@user315052)

【问题讨论】:

    标签: c++ buffer winsock send nonblocking


    【解决方案1】:

    在 while 循环的顶部,您已经在递减 nLeft,因此您无需从中减去 nPosition

    iResult = send(m_Socket, (char*)(buf + nPosition), nLeft, 0);
    

    在您的第二个函数中,当您将未发送的字节移动到数组的开头时,您应该使用memmove,因为您有重叠的区域(您正在将m_SendBuffer 的区域复制到自身中)。重叠如下图所示,其中一些A 会被复制到自身上。

    m_SendBuffer: [XXXXAAAAAAAAAAAA]
    mPosition: 4
    nLeft: 12
    

    我有点困惑为什么要实现DataSend() 以允许调用者在遇到WSAEWOULDBLOCK 时继续成功调用它。我建议修改接口以返回一个结果,让调用者知道它应该停止发送,并等待恢复发送的指示。

    您不需要nPosition &gt; 0 检查。

    您可以通过让数据的接收者不读取任何内容来强制发生这种情况。

    send 在非阻塞模式下绝对有可能发送的字节数少于请求的字节数。

    【讨论】:

    • 谢谢伙计。 DataSend()允许调用者一直调用的原因是因为这是一个游戏服务器,并没有自己处理请求,所以他将数据发送到另一个连接到SQL数据库的进程等。游戏服务器等待外部进程响应以允许玩家登录等
    • 您好,请检查我在第一次发布时发送的(FD_WRITE 事件)是否正确。我也对第一个函数进行了修复...
    • 另一个问题。我需要使用 memmove 而不是 memcpy 来重新定位 DataSendEvent() 中的缓冲区?
    • @youngrp:在DataSendEvent 中,使用memmove,而不是memcpy。我更新了帖子,谢谢。
    • 非常感谢!只需将memcpy 更改为memmove 其他都可以吗?你怎么看?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-27
    • 1970-01-01
    • 2011-03-03
    • 2012-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多