我正在使用以下函数来接收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;
}
答案 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()