此函数是否对套接字有问题?

Is this function doing something wrong with the sockets?

本文关键字:有问题 套接字 函数 是否      更新时间:2023-10-16

我使用以下函数接收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;
}

ReceiveBuffer()不应该调用WSAAsyncSelect()或设置SO_RCVBUF。 这是最初创建SOCKET的任何代码的责任。

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

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

但是,您的读取循环不考虑来自recv()的可能WSAEWOULDBLOCK错误,因此它可以再次调用recv()以继续读取。

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

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

但实际上,首先没有必要在每次调用ReceiveBuffer()时设置SO_RCVBUFrecv()返回当时当前可用的任何数据,最多为请求的缓冲区大小。 在任何给定的读取中,它不太可能返回接近 10MB 的数据。 所以你只是浪费了很多内存,没有真正的好处。 如果您在快速网络上,将套接字的内部缓冲区设置为 10MB 是一回事。 分配 10MB 的内存缓冲区以从每个recv()调用接收数据是另一回事。 应使用小得多的内存缓冲区。 1K 是常用的大小。

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

此外,如果调用方使用sBuffer参数的同一变量多次调用ReceiveBuffer(),则应在开始读取循环之前调用sBuffer.clear(),以确保不会将新数据追加到过时数据的末尾。

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

现在,综上所述,请尝试更多类似的东西(我假设优雅断开连接是数据结束指示器,因为这就是您的原始代码正在做的事情 - 您需要认真考虑不同的协议设计!

bool ReceiveBuffer(SOCKET socket, string& sBuffer)
{
sBuffer.clear();
/*
int iBufSize = 1024 * 1024 * 10; //10MB
setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&iBufSize, sizeof(iBufSize));
if (getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&iBufSize, sizeof(iBufSize)) == SOCKET_ERROR)
WriteLog("Unable to GET buffer receiving size");
*/
char* buf = (char*) malloc(1024);
if (!buf)
{
WriteLog("Unable to allocate memory"); 
return false;
}
int iCharsRead;
bool bRet = true;
do
{
iCharsRead = recv(socket, buf, 1024, 0);
if (iCharsRead > 0)
{
sBuffer.append(buf, iCharsRead);
}
else if (iCharsRead == 0)
{
// socket disconnected gracefully
break;
}
else
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
// socket error!
WriteLog("Unable to read from socket"); 
bRet = false;
break;
}
// socket is non-blocking and there is no data available
// at this moment.  Call recv() again...
// optional: call select() to wait for new data to arrive
// before calling recv() again.  For instance, this will
// allow you to fail the function if no new data arrived
// within a timeout period...
//
/*
fd_set fd;
FD_ZERO(&fd);
FD_SET(socket, &fd);
timeval tv;
tv.tv_sec = 30;
tv.tv_usec = 0;
int ret = select(0, &fd, NULL, NULL, &tv);
if (ret <= 0)
{
if (ret == 0)
{
// timeout!
WriteLog("Timeout waiting for data from socket"); 
}
else
{
// socket error!
WriteLog("Unable to wait for data from socket"); 
}
bRet = false;
break;
}
*/
}
}
while (true);
free(buf);
return bRet;
}