gpt4 book ai didi

c++ - 在 UNIX 中通过 recv/send 交换数据时如何正确使用缓冲区?

转载 作者:行者123 更新时间:2023-11-30 02:13:54 25 4
gpt4 key购买 nike

我正在进行一个简单的聊天,必须将一个成员发出的短信发送给所有其他成员。每个人都必须收到的消息格式是“[IP]:你好!”。此外,当有人连接或断开连接时,服务器必须通知所有人:分别为“[IP] 已连接”和“[IP] 未连接”。

这里有一段代码实现了服务端的这个功能。你可以开始寻找,因为有“问题在这里”评论的行:

while (true)
{

// select() for reading

static constexpr int bufferSize = 1024;
static char buffer[bufferSize];

// this is for getting IP-address of sender
static sockaddr_in sockAddr;
static socklen_t sockAddrSize;

if (FD_ISSET(masterSocket, &set)) // masterSocket is a socket that establishes connections
{
sockAddrSize = sizeof(sockAddr);
int slaveSocket = accept(masketSocket, &sockAddr, &sockAddrSize); // slaveSocket is a client socket

// setting a slaveSocket non-blocking

sprintf(buffer, "[%d.%d.%d.%d] has connected\n",
(sockAddr.sin_addr.s_addr & 0x000000FF),
(sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
(sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
(sockAddr.sin_addr.s_addr & 0xFF000000) >> 24);

for (const auto &socket : slaveSockets)
send(socket, buffer, strlen(buffer), MSG_NOSIGNAL);

slaveSockets.insert(slaveSocket);
}

for (const auto &socket : slaveSockets)
{
if (FD_ISSET(socket, &set))
continue;

static int recvSize = recv(socket, buffer, bufferSize, MSG_NOSIGNAL);

if (recvSize == 0 && errno != EAGAIN)
{
sockAddrSize = sizeof(sockAddr);
getsockname(socket, (sockaddr *) &sockAddr, &sockAddrSize);
sprintf(buffer, "[%d.%d.%d.%d] has disconnected\n",
(sockAddr.sin_addr.s_addr & 0x000000FF),
(sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
(sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
(sockAddr.sin_addr.s_addr & 0xFF000000) >> 24);

shutdown(socket, SHUT_RDWR);
close(socket);
slaveSockets.erase(socket);

for (const auto &socket : slaveSockets)
send(socket, buffer, strlen(buffer), MSG_NOSIGNAL);
}
else if (recvSize > 0) // THE PROBLEM IS HERE
{
static char reply[bufferSize];
sockAddrSize = sizeof(&sockAddr);
getsocklen(socket, (sockaddr *) &sockAddr, &sockAddrSize);
sprintf(reply, "[%d.%d.%d.%d]: %s\n",
(sockAddr.sin_addr.s_addr & 0x000000FF),
(sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
(sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
(sockAddr.sin_addr.s_addr & 0xFF000000) >> 24,
buffer);

int senderSocket = socket;
for (const auto &socket : slaveSockets)
{
if (socket == senderSocket)
continue;

send(socket, reply, strlen(reply), MSG_NOSIGNAL); // even tried the "strlen(reply) + 1"
}
}
}

}

问题是接收者错误地输出了每条消息:它被完整地输出但最后也有缓冲区旧值的结尾。例如:

客户端 A 已连接。

客户端 B 已连接。客户端 A 收到“[127.0.0.1] 已连接”。

客户端 A 发送了“你好”。客户端 B 收到“[127.0.0.1]: hello\n0.1] 已连接\n”。

客户端 B 发送了“怎么了?”。客户端 A 收到“[127.0.0.1]:怎么了?\n已连接\n”。

客户端 A 已断开连接。客户端 B 收到“[127.0.0.1] 已断开连接”。

如你所见,连接/断开信息总是正确输出,但聊天是错误的:它最后包含部分连接/断开信息。

我真诚地相信我正确使用了缓冲区,无法理解我做错了什么。

最佳答案

recv 返回后,buffer 不是以 null 结尾的 C 字符串。这是合乎逻辑的——如果你传输二进制数据呢?然后你会想要 recv 正好(消息长度)字节并且不附加任何零字节。

请注意,在 send 中发送终止空字节是错误的做法 - 您的接收方依赖于发送方附加此零字节,但如果发送方是恶意的,那么他可能不会附加零字节并且导致各种错误和漏洞 - 包括 DoS 攻击和远程代码执行。

您可能仍然依赖于发送方附加零字节,但是您应该将 bufferSize-1 作为缓冲区长度传递给 recv,并在调用 recv 之后 设置 reply[bufferSize-1]=0。但也许这仍然不是最好的做法:众多其他选项之一是将“消息长度”作为 32 位未签名整数传递,检查最大长度(例如,没有消息大于 1024 个字符,并且如果是,则不接收任何东西,只关闭套接字),并且 recv 恰好将传递的“消息长度”字节传递给缓冲区。如果您打算将缓冲区用作 C 风格字符串,您仍然需要附加终止空字节。

编辑:重要!如果您使用 TCP (SOCK_STREAM),请始终使用消息长度:消息可能(并且有一天会)被 recv 分段读取。您绝对应该自己将它们连接成完整的消息。

关于c++ - 在 UNIX 中通过 recv/send 交换数据时如何正确使用缓冲区?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58680735/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com