在我的C++程序中,我发送了许多小的(< KB) UDP消息。当我测量这类UDP发送的性能时,我看到它们中的大多数都在300毫秒以下完成,但偶尔(每到150次),它需要3秒。请注意,这些测量是在wi网络上进行的,这可能是偶尔延迟的原因。然而,如果我想支持wi场景,我怀疑增加发送缓冲区将是我的第一步。我最初的研究指出,通过net.core.rmem_max
和net.core.rmem_default
来增加UDP缓冲区的大小,我做到了:
$sudo sysctl net.core.rmem_max
net.core.rmem_max = 26214400
$sudo sysctl net.core.rmem_default
net.core.rmem_default = 26214400
然而,这似乎对我的程序没有任何影响。接下来,我尝试在代码:setsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF, &size, size_len)
中设置缓冲区大小,其中的大小是26214400
。这解决了问题-没有更多的延迟包。但是,在通过getsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &size, &size_len)
检查缓冲区大小之后,我发现缓冲区被设置为5228800
。
因此,我的问题是:增加UDP发送缓冲区的适当方式是什么?
我的环境:
cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
这个问题只出现在我使用wi时,这是我想要支持的用例。换句话说,在对以太网接口进行测试时,我看不到偶尔缓慢的发送。
下面是我与UDP相关的源代码:
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdexcept>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <boost/log/trivial.hpp>
#include "udp_sender.h"
void soc_get_so_snd_buf(int socket_fd)
{
unsigned int size = 0;
socklen_t size_len = sizeof(size);
if(getsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &size, &size_len) == -1 ) {
std::string err_msg = "Failed to get the socket send buffer for socket: " + std::to_string(socket_fd);
BOOST_LOG_TRIVIAL(error) << err_msg;
throw std::runtime_error(err_msg);
}
BOOST_LOG_TRIVIAL(debug) << "Send buffer size (SO_SNDBUF) is: " << size;
}
void soc_set_so_snd_buf(int sock_fd, size_t size)
{
socklen_t size_len = sizeof(size);
if (setsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF, &size, size_len) == -1) {
std::string err_msg = "Failed to set the socket send buffer to " + std::to_string(size);
BOOST_LOG_TRIVIAL(error) << err_msg;
throw std::runtime_error(err_msg);
}
}
UDPSender::UDPSender(int id) : my_id(id) {
char my_ip[100];
sprintf(my_ip, "10.1.1.%d", id);
send_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(send_socket_fd < 0)
throw std::runtime_error("Failed to create a send socket.");
int enable = 1;
if (setsockopt(send_socket_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
throw std::runtime_error("Failed to assign REUSADDR option.");
}
soc_get_so_snd_buf(send_socket_fd);
soc_set_so_snd_buf(send_socket_fd, 26214400); // setting this to 26214400 results in 5228800 which seems to solve my issue for now.
soc_get_so_snd_buf(send_socket_fd);
struct sockaddr_in srcAddr{};
memset(&srcAddr, 0, sizeof(srcAddr));
srcAddr.sin_family = AF_INET;
srcAddr.sin_port = htons(0); // Any port will do.
inet_pton(AF_INET, my_ip, &srcAddr.sin_addr);
if(bind(send_socket_fd, (struct sockaddr*)&srcAddr, sizeof(srcAddr)) < 0) {
BOOST_LOG_TRIVIAL(error) << "Failed to bind sending socket " << strerror(errno);
throw std::runtime_error("Failed to bind a send socket.");
}
}
UDPSender::~UDPSender() {
close(send_socket_fd);
send_socket_fd = -1;
}
ssize_t UDPSender::send(
int dest_id,
int dest_port,
char* msg,
size_t msg_size) const {
char dest_ip[100];
struct sockaddr_in dest_addr{};
sprintf(dest_ip, "10.1.1.%d", dest_id);
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(dest_port);
inet_pton(AF_INET, dest_ip, &dest_addr.sin_addr);
ssize_t bytes_sent = sendto(
send_socket_fd,
msg,
msg_size,
0,
(struct sockaddr*)&dest_addr, sizeof(dest_addr));
if (bytes_sent < 0) {
std::string error_msg = "Failed to send from: " +
std::to_string(my_id) +
" to: " +
std::to_string(dest_id) + " errno: " + std::to_string(errno) + " ";
error_msg += strerror(errno);
if (errno == EPERM) {
error_msg += "; error was due iptables bock rule - expected for some targets.";
BOOST_LOG_TRIVIAL(debug) << error_msg;
return 0;
} else {
BOOST_LOG_TRIVIAL(error) << error_msg;
throw std::runtime_error(error_msg);
}
}
if (bytes_sent != msg_size) {
BOOST_LOG_TRIVIAL(error) << "Failed to send the expected number of bytes, wanted :"
<< msg_size << " sent " << bytes_sent;
throw std::runtime_error("Failed to send the expected number of bytes.");
}
return bytes_sent;
}
测试:
#include "udp_sender.h"
#include "message_factory.h"
#include "heartbeats.h"
#include <thread>
#include <chrono>
#include "gtest/gtest.h"
#include <boost/log/trivial.hpp>
using namespace std::chrono;
TEST(UDPSenderTest, Broadcast) {
int sender_id = 0;
UDPSender sender = UDPSender(sender_id);
char serialized[65000];
Heartbeat msg = Heartbeat(sender_id, Peers{Node{2, 3}}, false);
long serialized_size = MessageFactory::serialize_heartbeat(msg, serialized);
auto start_cycle = high_resolution_clock::now();
for(int i=0; i<256; i++) {
auto start = high_resolution_clock::now();
sender.send(i, 7777, serialized, serialized_size);
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start);
if (duration.count() > 300) {
BOOST_LOG_TRIVIAL(error) << "Sent broadcast to " << i << " in " << duration.count() << " milliseconds";
}
}
auto stop_cycle = high_resolution_clock::now();
auto duration = duration_cast<milliseconds>(stop_cycle - start_cycle);
BOOST_LOG_TRIVIAL(debug) << "Completed broadcast cycle in " << duration.count() << " milliseconds";
}
当我不设置套接字选项,而是依赖Linux配置时,输出:
[ RUN ] UDPSenderTest.Broadcast
37: [2022-10-06 09:56:08.183035] [0x00007f5f15f8b740] [debug] Send buffer size (SO_SNDBUF) is: 212992
37: [2022-10-06 09:56:11.256173] [0x00007f5f15f8b740] [debug] Sent broadcast to 247 in milliseconds 3069
37: [2022-10-06 09:56:11.256590] [0x00007f5f15f8b740] [debug] Completed broadcast cycle in 3073 milliseconds
发布于 2022-10-07 20:02:08
您设置了错误的内核参数。
net.core.rmem_default
和net.core.rmem_max
参数分别设置UDP 接收缓冲区的默认大小和最大大小。UDP 发送缓冲区的相应参数是net.core.wmem_default
和net.core.wmem_max
。
通常,为您的程序设置UDP发送缓冲区的正确方法是使用SO_SNDBUF
套接字选项,您设置的值将受到net.core.wmem_max
内核参数的限制。然后,如果需要一个大于最大值的值,则可以增加net.core.wmem_max
的值。
您通常不希望修改net.core.wmem_default
或net.core.rmem_default
,因为这将影响系统上的其他进程。
https://stackoverflow.com/questions/73976502
复制相似问题