这个功能是否与套接字有关?

时间:2016-12-01 16:23:24

标签: c++ sockets winsock2

我正在使用以下函数来接收XML文件一段时间,但现在已经出错了一段时间,我认为问题出在客户的网络上。我不确定,这只是猜测。 当他们尝试向我发送大于13KB的XML文件时,有时会发生这种情况 - 收到的缓冲区包含这样的垃圾:

...
    <Identifiers>
      <Identifier>
        <PID>E3744</PID>
      </Identifier>
      <Identifier IDType="SHC">
        <PID>10021020</PID>
      </Identifier>
      <Identifier><*X| Å  Å    Ÿòc PV“R¢ E ·Â÷@ @€ˆ
þõ
øæ=Ì×KåÅôdËÞ¦P s÷j  
        <PID>1002102-0</PID>
      </Identifier>
      <Identifier>
        <PID>1002102</PID>
      </Identifier>
    </Identifiers>
...

这是功能:

bool ReceiveBuffer(HWND hDlg, const SOCKET& socket, string& sBuffer)
{
    WSAAsyncSelect(socket, hDlg, WM_WINSOCK, FD_CLOSE);
    int iBufSize = 10000000; //10MB
    int iBufVarSize = sizeof(iBufSize);

    if (setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&iBufSize, iBufVarSize) == SOCKET_ERROR)
        if (getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&iBufSize, &iBufVarSize) == SOCKET_ERROR)
            WriteLog("Unable to GET buffer receiving size");

    char* buf = (char*)MALLOCZ(iBufSize);

    if (!buf)
    {
        WriteLog("Unable to allocate memory"); 
        return false;
    }

    int iCharsRead = 0;

    do
    {
        memset(buf, 0, iBufSize);
        iCharsRead = recv(socket, buf, iBufSize, 0);

        if (iCharsRead > 0)
            sBuffer.append(buf, iCharsRead);
    }
    while (iCharsRead > 0);

    FREE(buf);
    buf = NULL;

    return true;
}

1 个答案:

答案 0 :(得分:1)

ReceiveBuffer()不应该致电WSAAsyncSelect()或设置SO_RCVBUF。这是最初创建SOCKET

的代码的责任

但更重要的是,根据文档,WSAAsyncSelect()将套接字置于非阻塞模式:

  

WSAAsyncSelect函数会自动将套接字s设置为非阻塞模式,而不管lEvent的值是多少。

但是,您的阅读循环并未考虑来自WSAEWOULDBLOCK的{​​{1}}错误,因此可以再次致电recv()以继续阅读。

recv()假设如果ReceiveBuffer()成功,那么实际的缓冲区大小实际上是请求的大小,这是无法保证的。因此,无论setsockopt()是成功还是失败,您都需要根据文档调用getsockopt()

  

SO_RCVBUF和SO_SNDBUF
  当Windows套接字实现支持setsockopt()SO_RCVBUF选项时,应用程序可以请求不同的缓冲区大小(更大或更小)。 即使实施未提供所需的全部金额,对SO_SNDBUF的调用也可以成功。应用程序必须使用相同的选项调用setsockopt以检查实际提供的缓冲区大小。

但实际上,首先在getsockopt的每次通话中设置SO_RCVBUF都不是必需的。 ReceiveBuffer()返回当时可用的任何数据,直到请求的缓冲区大小。在任何给定的读取中,它几乎不可能返回接近10MB的数据。所以你只是浪费了大量的记忆而没有真正的好处。如果您在快速网络上,将套接字内部缓冲区设置为10MB是一回事。分配10MB的内存缓冲区来接收来自每个recv()调用的数据是另一回事。您应该使用更小的内存缓冲区。 1K是常用的尺寸。

但除此之外,无论使用何种缓冲区大小,recv()都会在无限循环中读取任意字节,直到套接字断开连接或出错(并且不考虑非阻塞错误)。当套接字最终断开连接/错误时,ReceiveBuffer()返回true而不是false,因此调用者不知道出现了错误,或者ReceiveBuffer()可能不完整。

此外,如果调用者使用sBuffer参数的相同变量多次调用ReceiveBuffer(),则应在开始读取循环之前调用sBuffer以确保不附加新数据到陈旧数据的末尾。

现在,以上所有内容都只是代码逻辑的技术问题。但是也有一个语义元素。 XML的长度有限,但您当前的代码无法知道实际长度是多少。发送者有责任在XML停止发送时告知接收者。这可以通过在发送XML本身之前发送XML的长度,因此接收器知道要期望多少字节。或者可以通过在XML的末尾发送一个唯一的分隔符(如空终止符),这样接收者可以在看到分隔符时停止读取。或者可以通过优雅地关闭XML末尾的连接(这是一个坏主意,因为接收器可以区分数据结束和数据丢失)。但它必须做点什么。

现在,所有这些都说了,尝试更像这样的东西(我假设一个优雅的断开连接是数据结束指示器,因为这是你的原始代码正在做的事情 - 你需要认真对待考虑不同的协议设计!):

sBuffer.clear()