所以我目前正在开发一个使用socket的应用程序,现在一切正常,我只想知道是否有更有效的方式来发送数据和数据的长度而不是发送两次。
即:将所有内容发送到一个字符串中。
void SendPackage(const SOCKET sock, const std::string package)
{
int length = package.lenth();
send(sock, std::to_string(length).c_str(), 10, 0); //Buffer length is 10 assuming data length
send(sock, package.c_str(), length, 0); //will never be greater than 9,999,999,999
}
void ReceivePackage(const SOCKET sock, std::string &package, int bufferLength)
{
std::vector<char> recvBuffer(10);
int length, bytesProcessed;
recv(sock, &recvBuffer[0], 10, 0); //Receiving length
length = atoi(&recvBuffer[0]);
recvBuffer.resize(length);
for (int i = 0; i < length; i += bytesProcessed)
{
bytesProcessed = recv(sock, &recvBuffer[0] + i, bufferLength, 0);
if (bytesProcessed < 0) break;
}
package = &recvBuffer[0];
}
就套接字接口而言,您不会发送两次。套接字只需要用一个或多个send
调用一端接收你所有的东西,并在它自己的时间发送出去(我假设你没有超过第一次调用的套接字缓冲区,这是一个相当安全的假设)。同样,您不需要将recv
调用与send
调用匹配。你可以一次性完成recv
,然后再打破它,但实际上,没有大的收获。
无论你先打包然后发送它,还是两次打电话给send
,这真的是一种洗漱。创建一个长度连接到开头的新字符串所需的时间和RAM不会比仅调用send
两次更有效。
但是,你应该发送它所引入的int
的长度,而不是字符串。这样它总是会占用sizeof(int)
字节。 10
的缓冲区长度通常不正确,导致读取字符串末尾。例如:
send(sock, &length, sizeof(length), 0);
和
recv(sock, &length, sizeof(length), 0); //Receiving length
send(sock, std::to_string(length).c_str(), 10, 0);
这是UB。你告诉它数据有10个字节,但这不正确。除此之外,不需要将int转换为字符串然后发送字符串。刚刚发送了int,并且为了避免任何平台不兼容,我建议使用固定大小类型:
std::int32_t length = package.length();
send(sock, &length, sizeof(length), 0);
在接收端,您必须确保实际等待所有字节在那里。你不能简单地调用recv
并假设它为你提供了所需的所有字节。你需要在循环中调用它并检查它的返回值,它会告诉你你得到了多少字节。要接收length
,您需要将适当的字节数读入缓冲区,然后将该缓冲区重新解释为您选择的类型(例如std::int32_t
。
另外,您应该确保int的字节顺序是正确的。它是将big-endian用于网络数据的事实上的标准,但在一天结束时,这取决于你。不是标准(afaik)的一部分,但在通用平台上可用的辅助函数htonl
和ntohl
,代表“主机到网络,长”和“网络到主机,长”。他们接受并返回一个std::int32_t
,你可以简单地使用它们来确保字节顺序适用于双方。
发件人:
std::int32_t length = htonl(package.length());
send(sock, &length, sizeof(length), 0);
接收器:
// after successfully reading all bytes for the length value into an adequately sized buffer:
std::int32_t length = ntohl(*reinterpret_cast<std::int32_t*>(buffer));