基于C++的UDP网络通信系统设计与实现
前言
在网络编程领域,UDP(User Datagram Protocol,用户数据报协议)作为一种无连接的传输层协议,以其高效、低延迟的特性在实时性要求高的应用场景中占据重要地位。与TCP协议相比,UDP不需要建立连接,不保证数据包的顺序和可靠性,但正是这种"轻量级"特性使其在视频流、在线游戏、DNS查询等领域得到广泛应用。
本文将深入探讨如何从零开始构建一个完整的UDP通信系统,涵盖服务器端、客户端的设计与实现,包括套接字编程的核心概念、关键系统调用、错误处理机制以及实际应用中的注意事项。通过本文的学习,读者不仅能够掌握UDP网络编程的基本技能,还能深入理解网络通信的底层原理。
本文实现的UDP通信系统具有以下特点:
- 完整的服务器/客户端架构
- 详细的错误处理和日志记录
- 可配置的服务器参数
- 跨平台兼容性考虑
- 丰富的代码示例和详细注释
一、UDP服务器UdpServer.hpp
1.1 基本框架设计
UDP服务器的设计需要遵循模块化、可扩展的原则。我们将服务器封装为一个类,包含初始化、运行和清理等基本功能。
cpp
#ifndef UDPSERVER_HPP
#define UDPSERVER_HPP
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <thread>
#include <vector>
#include <memory>
#include <atomic>
#include <functional>
#include "Log.hpp"
class UdpServer {
private:
int port_; // 服务器端口
int sockfd_; // 套接字描述符
std::atomic<bool> is_running_; // 服务器运行状态
struct sockaddr_in server_addr_; // 服务器地址结构
struct sockaddr_in client_addr_; // 客户端地址结构
socklen_t client_addr_len_; // 客户端地址长度
// 服务器配置参数
size_t buffer_size_; // 缓冲区大小
int timeout_sec_; // 接收超时时间(秒)
int timeout_usec_; // 接收超时时间(微秒)
bool reuse_addr_; // 是否重用地址
public:
// 构造函数
explicit UdpServer(int port = 8080);
// 析构函数
~UdpServer();
// 禁止拷贝构造和赋值
UdpServer(const UdpServer&) = delete;
UdpServer& operator=(const UdpServer&) = delete;
// 初始化服务器
bool Init();
// 运行服务器
void Run();
// 停止服务器
void Stop();
// 设置配置参数
void SetBufferSize(size_t size) { buffer_size_ = size; }
void SetTimeout(int sec, int usec = 0) {
timeout_sec_ = sec;
timeout_usec_ = usec;
}
void SetReuseAddr(bool reuse) { reuse_addr_ = reuse; }
private:
// 创建套接字
bool CreateSocket();
// 绑定地址
bool BindAddress();
// 设置套接字选项
bool SetSocketOptions();
// 处理接收到的数据
virtual void ProcessData(const char* data, ssize_t len,
const struct sockaddr_in& client_addr);
// 发送响应
bool SendResponse(const char* data, ssize_t len,
const struct sockaddr_in& client_addr);
// 清理资源
void Cleanup();
};
#endif // UDPSERVER_HPP
1.2 初始化函数Init详解
初始化函数是服务器启动的第一步,它负责套接字创建、地址绑定和选项设置等关键操作。
cpp
bool UdpServer::Init() {
// 1. 创建日志实例
Logger::Instance().Init("udp_server.log", LogLevel::INFO);
LOG_INFO("Starting UDP server initialization...");
// 2. 创建套接字
if (!CreateSocket()) {
LOG_ERROR("Failed to create socket");
return false;
}
// 3. 设置套接字选项
if (!SetSocketOptions()) {
LOG_ERROR("Failed to set socket options");
close(sockfd_);
return false;
}
// 4. 绑定地址
if (!BindAddress()) {
LOG_ERROR("Failed to bind address");
close(sockfd_);
return false;
}
// 5. 初始化客户端地址结构
memset(&client_addr_, 0, sizeof(client_addr_));
client_addr_len_ = sizeof(client_addr_);
LOG_INFO("UDP server initialized successfully on port %d", port_);
LOG_INFO("Buffer size: %zu bytes", buffer_size_);
LOG_INFO("Timeout: %d seconds %d microseconds", timeout_sec_, timeout_usec_);
return true;
}
bool UdpServer::CreateSocket() {
// 使用AF_INET表示IPv4,SOCK_DGRAM表示UDP协议
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd_ < 0) {
LOG_ERROR("Socket creation failed: %s", strerror(errno));
return false;
}
LOG_DEBUG("Socket created successfully, fd: %d", sockfd_);
return true;
}
bool UdpServer::SetSocketOptions() {
int optval = 1;
// 设置地址重用选项,避免"Address already in use"错误
if (reuse_addr_) {
if (setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR,
&optval, sizeof(optval)) < 0) {
LOG_WARN("Failed to set SO_REUSEADDR: %s", strerror(errno));
// 注意:这不是致命错误,可以继续运行
} else {
LOG_DEBUG("SO_REUSEADDR set successfully");
}
}
// 设置接收超时
if (timeout_sec_ > 0 || timeout_usec_ > 0) {
struct timeval tv;
tv.tv_sec = timeout_sec_;
tv.tv_usec = timeout_usec_;
if (setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO,
&tv, sizeof(tv)) < 0) {
LOG_WARN("Failed to set receive timeout: %s", strerror(errno));
} else {
LOG_DEBUG("Receive timeout set to %ld.%06ld seconds",
tv.tv_sec, tv.tv_usec);
}
}
// 设置发送缓冲区大小
int send_buf_size = 1024 * 1024; // 1MB
if (setsockopt(sockfd_, SOL_SOCKET, SO_SNDBUF,
&send_buf_size, sizeof(send_buf_size)) < 0) {
LOG_WARN("Failed to set send buffer size: %s", strerror(errno));
}
// 设置接收缓冲区大小
int recv_buf_size = 1024 * 1024; // 1MB
if (setsockopt(sockfd_, SOL_SOCKET, SO_RCVBUF,
&recv_buf_size, sizeof(recv_buf_size)) < 0) {
LOG_WARN("Failed to set receive buffer size: %s", strerror(errno));
}
return true;
}
bool UdpServer::BindAddress() {
// 初始化服务器地址结构
memset(&server_addr_, 0, sizeof(server_addr_));
// 设置地址族为IPv4
server_addr_.sin_family = AF_INET;
// 设置端口,使用htons进行字节序转换
server_addr_.sin_port = htons(port_);
// 设置IP地址为INADDR_ANY,表示监听所有网络接口
server_addr_.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定套接字到指定地址和端口
if (bind(sockfd_, (struct sockaddr*)&server_addr_,
sizeof(server_addr_)) < 0) {
LOG_ERROR("Bind failed on port %d: %s", port_, strerror(errno));
return false;
}
// 获取实际绑定的地址信息
struct sockaddr_in actual_addr;
socklen_t actual_len = sizeof(actual_addr);
if (getsockname(sockfd_, (struct sockaddr*)&actual_addr, &actual_len) == 0) {
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &actual_addr.sin_addr, ip_str, sizeof(ip_str));
LOG_INFO("Server bound to %s:%d", ip_str, ntohs(actual_addr.sin_port));
}
return true;
}
1.3 关键系统调用详解
1.3.1 inet_addr函数
inet_addr函数用于将点分十进制表示的IPv4地址转换为网络字节序的32位整数。虽然本文代码中使用的是inet_pton(更安全的版本),但理解inet_addr仍然很重要。
cpp
// inet_addr的使用示例
const char* ip_str = "192.168.1.100";
in_addr_t addr = inet_addr(ip_str);
if (addr == INADDR_NONE) {
LOG_ERROR("Invalid IP address: %s", ip_str);
} else {
LOG_DEBUG("IP %s converted to network byte order: 0x%08x",
ip_str, addr);
// 转换回点分十进制格式
struct in_addr addr_struct;
addr_struct.s_addr = addr;
char* ip_str_back = inet_ntoa(addr_struct);
LOG_DEBUG("Converted back to string: %s", ip_str_back);
}
// 现代推荐使用inet_pton(更安全,支持IPv6)
struct sockaddr_in addr;
if (inet_pton(AF_INET, ip_str, &addr.sin_addr) <= 0) {
LOG_ERROR("Invalid IP address format: %s", ip_str);
}
1.3.2 bzero和memset函数
bzero是BSD系统中用于将内存区域清零的函数,而memset是标准C库函数,功能更通用。
cpp
// bzero的使用(传统方式)
struct sockaddr_in addr;
bzero(&addr, sizeof(addr)); // 将整个结构体清零
// memset的等效用法
memset(&addr, 0, sizeof(addr)); // 更标准的做法
// memset的更多用途
char buffer[1024];
// 全部设置为0
memset(buffer, 0, sizeof(buffer));
// 全部设置为特定值
memset(buffer, 'A', sizeof(buffer));
// 部分设置
memset(buffer, 0, 100); // 只清空前100字节
// 性能比较:对于大内存块,memset通常经过优化,性能更好
1.4 服务器运行函数Run
Run函数是服务器的核心,负责循环接收客户端请求并处理。
cpp
void UdpServer::Run() {
if (sockfd_ < 0) {
LOG_ERROR("Cannot run server: socket not initialized");
return;
}
is_running_ = true;
LOG_INFO("UDP server started, waiting for connections...");
// 分配接收缓冲区
std::vector<char> buffer(buffer_size_);
// 主循环
while (is_running_) {
// 重置客户端地址信息
memset(&client_addr_, 0, sizeof(client_addr_));
client_addr_len_ = sizeof(client_addr_);
// 接收数据
ssize_t recv_len = recvfrom(sockfd_, buffer.data(), buffer.size() - 1,
0, (struct sockaddr*)&client_addr_,
&client_addr_len_);
if (recv_len < 0) {
// 处理接收错误
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 超时,继续循环
continue;
} else if (errno == EINTR) {
// 被信号中断
LOG_DEBUG("recvfrom interrupted by signal");
continue;
} else {
LOG_ERROR("recvfrom failed: %s", strerror(errno));
break;
}
} else if (recv_len == 0) {
// UDP中recvfrom返回0表示收到了0字节的数据包
LOG_DEBUG("Received empty datagram");
continue;
}
// 确保字符串以null结尾
buffer[recv_len] = '\0';
// 获取客户端信息
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr_.sin_addr,
client_ip, sizeof(client_ip));
uint16_t client_port = ntohs(client_addr_.sin_port);
LOG_DEBUG("Received %zd bytes from %s:%d",
recv_len, client_ip, client_port);
LOG_DEBUG("Data: %s", buffer.data());
// 处理数据
ProcessData(buffer.data(), recv_len, client_addr_);
}
LOG_INFO("UDP server stopped");
Cleanup();
}
void UdpServer::ProcessData(const char* data, ssize_t len,
const struct sockaddr_in& client_addr) {
// 默认实现:原样返回数据(echo服务器)
LOG_DEBUG("Processing %zd bytes of data", len);
// 构造响应
std::string response = "Server received: ";
response.append(data, len);
// 发送响应
if (!SendResponse(response.c_str(), response.length(), client_addr)) {
LOG_ERROR("Failed to send response to client");
}
}
bool UdpServer::SendResponse(const char* data, ssize_t len,
const struct sockaddr_in& client_addr) {
if (len <= 0) {
LOG_WARN("Attempting to send empty data");
return true; // 空数据发送"成功"
}
// 发送数据
ssize_t sent_len = sendto(sockfd_, data, len, 0,
(const struct sockaddr*)&client_addr,
sizeof(client_addr));
if (sent_len < 0) {
LOG_ERROR("sendto failed: %s", strerror(errno));
return false;
}
if (sent_len != len) {
LOG_WARN("Partial send: %zd of %zd bytes sent", sent_len, len);
}
// 获取客户端信息用于日志
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
uint16_t client_port = ntohs(client_addr.sin_port);
LOG_DEBUG("Sent %zd bytes to %s:%d", sent_len, client_ip, client_port);
return true;
}
1.5 recvfrom和sendto函数深度解析
1.5.1 recvfrom函数
recvfrom是UDP接收数据的核心函数,它不仅可以接收数据,还能获取发送者的地址信息。
cpp
/**
* recvfrom函数原型:
* ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
* struct sockaddr *src_addr, socklen_t *addrlen);
*
* 参数说明:
* - sockfd: 套接字描述符
* - buf: 接收缓冲区
* - len: 缓冲区大小
* - flags: 标志位,常用值:
* * 0: 默认行为
* * MSG_WAITALL: 等待所有数据(对UDP通常无效)
* * MSG_DONTWAIT: 非阻塞模式
* * MSG_PEEK: 查看数据但不从缓冲区移除
* - src_addr: 发送方地址(输出参数)
* - addrlen: 地址长度(输入输出参数)
*
* 返回值:
* - 成功:接收到的字节数
* - 失败:-1,设置errno
* - 连接关闭(TCP)或空数据包(UDP):0
*/
// recvfrom的完整示例
void ReceiveExample(int sockfd) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[4096];
// 设置接收超时
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// 接收数据
ssize_t recv_len = recvfrom(sockfd, buffer, sizeof(buffer) - 1,
MSG_DONTWAIT, // 非阻塞模式
(struct sockaddr*)&client_addr, &addr_len);
if (recv_len > 0) {
buffer[recv_len] = '\0';
// 获取客户端信息
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));
uint16_t port = ntohs(client_addr.sin_port);
LOG_INFO("Received from %s:%d: %s", ip_str, port, buffer);
// 处理不同的消息类型
ProcessMessage(buffer, recv_len, client_addr);
} else if (recv_len == 0) {
LOG_DEBUG("Received empty datagram");
} else {
// 错误处理
if (errno == EAGAIN || errno == EWOULDBLOCK) {
LOG_DEBUG("No data available (non-blocking)");
} else if (errno == EINTR) {
LOG_DEBUG("Interrupted by signal");
} else {
LOG_ERROR("Receive error: %s", strerror(errno));
}
}
}
// 处理不同类型的消息
void ProcessMessage(const char* data, ssize_t len,
const struct sockaddr_in& client_addr) {
// 简单的协议处理示例
if (len >= 4 && strncmp(data, "PING", 4) == 0) {
LOG_DEBUG("Received PING request");
SendResponse("PONG", 4, client_addr);
} else if (len >= 4 && strncmp(data, "TIME", 4) == 0) {
time_t now = time(nullptr);
std::string time_str = ctime(&now);
SendResponse(time_str.c_str(), time_str.length(), client_addr);
} else if (len >= 7 && strncmp(data, "ECHO ", 5) == 0) {
// 回显消息内容
SendResponse(data + 5, len - 5, client_addr);
} else {
std::string response = "Unknown command: ";
response.append(data, len);
SendResponse(response.c_str(), response.length(), client_addr);
}
}
1.5.2 sendto函数
sendto是UDP发送数据的核心函数,用于向指定地址发送数据报。
cpp
/**
* sendto函数原型:
* ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
* const struct sockaddr *dest_addr, socklen_t addrlen);
*
* 参数说明:
* - sockfd: 套接字描述符
* - buf: 发送缓冲区
* - len: 要发送的数据长度
* - flags: 标志位,常用值:
* * 0: 默认行为
* * MSG_DONTWAIT: 非阻塞模式
* * MSG_CONFIRM: 确认路由有效(Linux特有)
* * MSG_MORE: 还有更多数据要发送
* - dest_addr: 目标地址
* - addrlen: 地址长度
*
* 返回值:
* - 成功:发送的字节数(可能小于len)
* - 失败:-1,设置errno
*/
// sendto的完整示例
bool SendData(int sockfd, const void* data, size_t len,
const struct sockaddr_in& dest_addr) {
if (len == 0) {
LOG_WARN("Attempting to send zero-length data");
return true;
}
// 检查数据包大小(UDP最大约64KB,实际建议小于1500字节避免分片)
if (len > 65507) { // 65535 - 20(IP头) - 8(UDP头)
LOG_ERROR("Datagram too large: %zu bytes (max: 65507)", len);
return false;
}
if (len > 1400) {
LOG_WARN("Large datagram: %zu bytes (may be fragmented)", len);
}
// 分块发送大数据(如果需要)
const size_t MAX_CHUNK = 1400; // 避免IP分片的推荐大小
size_t total_sent = 0;
while (total_sent < len) {
size_t chunk_size = std::min(MAX_CHUNK, len - total_sent);
const char* chunk_start = static_cast<const char*>(data) + total_sent;
ssize_t sent = sendto(sockfd, chunk_start, chunk_size, 0,
(const struct sockaddr*)&dest_addr,
sizeof(dest_addr));
if (sent < 0) {
LOG_ERROR("Failed to send chunk: %s (sent %zu/%zu bytes)",
strerror(errno), total_sent, len);
return false;
}
total_sent += sent;
// 添加小延迟避免拥塞
if (chunk_size == MAX_CHUNK && total_sent < len) {
usleep(1000); // 1ms延迟
}
}
LOG_DEBUG("Successfully sent %zu bytes to %s:%d",
total_sent,
inet_ntoa(dest_addr.sin_addr),
ntohs(dest_addr.sin_port));
return true;
}
// 发送不同类型的消息
void SendVariousMessages(int sockfd, const struct sockaddr_in& dest_addr) {
// 1. 发送字符串
const char* text = "Hello, UDP Server!";
SendData(sockfd, text, strlen(text), dest_addr);
// 2. 发送二进制数据
struct BinaryData {
uint32_t magic;
uint16_t version;
uint8_t type;
uint8_t data[256];
} binary_msg;
binary_msg.magic = htonl(0xDEADBEEF);
binary_msg.version = htons(1);
binary_msg.type = 0x42;
memset(binary_msg.data, 0xAA, sizeof(binary_msg.data));
SendData(sockfd, &binary_msg, sizeof(binary_msg), dest_addr);
// 3. 发送结构化数据(JSON格式)
std::string json_msg = R"({
"command": "update",
"timestamp": )" + std::to_string(time(nullptr)) + R"(,
"data": {"temperature": 23.5, "humidity": 65.2}
})";
SendData(sockfd, json_msg.c_str(), json_msg.length(), dest_addr);
// 4. 发送带序列号的消息
for (int i = 0; i < 10; i++) {
std::string seq_msg = "Message #" + std::to_string(i);
SendData(sockfd, seq_msg.c_str(), seq_msg.length(), dest_addr);
// 添加延迟
usleep(100000); // 100ms
}
}
1.6 高级功能:多线程处理和连接管理
对于高性能UDP服务器,我们需要考虑多线程处理和客户端连接管理。
cpp
// 扩展UdpServer类,添加多线程支持
class AdvancedUdpServer : public UdpServer {
private:
std::vector<std::thread> worker_threads_;
std::atomic<int> thread_count_;
int max_workers_;
// 线程池和工作队列
std::queue<std::pair<std::vector<char>, sockaddr_in>> task_queue_;
std::mutex queue_mutex_;
std::condition_variable queue_cv_;
public:
AdvancedUdpServer(int port = 8080, int max_workers = 4)
: UdpServer(port), max_workers_(max_workers), thread_count_(0) {}
~AdvancedUdpServer() {
Stop();
}
bool Init() override {
if (!UdpServer::Init()) {
return false;
}
// 创建工作线程
for (int i = 0; i < max_workers_; i++) {
worker_threads_.emplace_back(&AdvancedUdpServer::WorkerThread, this, i);
}
LOG_INFO("Started %d worker threads", max_workers_);
return true;
}
void Run() override {
if (sockfd_ < 0) {
LOG_ERROR("Socket not initialized");
return;
}
is_running_ = true;
LOG_INFO("Advanced UDP server started on port %d", port_);
std::vector<char> buffer(buffer_size_);
while (is_running_) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
// 接收数据
ssize_t recv_len = recvfrom(sockfd_, buffer.data(),
buffer.size() - 1, 0,
(struct sockaddr*)&client_addr,
&addr_len);
if (recv_len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
} else if (errno == EINTR) {
continue;
} else {
LOG_ERROR("Receive error: %s", strerror(errno));
break;
}
}
if (recv_len > 0) {
buffer[recv_len] = '\0';
// 将任务加入队列
{
std::lock_guard<std::mutex> lock(queue_mutex_);
task_queue_.emplace(
std::vector<char>(buffer.begin(), buffer.begin() + recv_len),
client_addr
);
}
// 通知工作线程
queue_cv_.notify_one();
// 获取统计信息
if (task_queue_.size() > 10) {
LOG_WARN("Task queue size: %zu", task_queue_.size());
}
}
}
// 通知所有工作线程退出
queue_cv_.notify_all();
// 等待所有线程结束
for (auto& thread : worker_threads_) {
if (thread.joinable()) {
thread.join();
}
}
LOG_INFO("Advanced UDP server stopped");
Cleanup();
}
private:
void WorkerThread(int thread_id) {
thread_count_++;
LOG_DEBUG("Worker thread %d started", thread_id);
while (is_running_) {
std::pair<std::vector<char>, sockaddr_in> task;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
queue_cv_.wait(lock, [this]() {
return !task_queue_.empty() || !is_running_;
});
if (!is_running_ && task_queue_.empty()) {
break;
}
if (!task_queue_.empty()) {
task = std::move(task_queue_.front());
task_queue_.pop();
} else {
continue;
}
}
// 处理任务
ProcessTask(task.first, task.second, thread_id);
}
thread_count_--;
LOG_DEBUG("Worker thread %d stopped", thread_id);
}
void ProcessTask(const std::vector<char>& data,
const sockaddr_in& client_addr,
int thread_id) {
// 获取客户端信息
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr,
client_ip, sizeof(client_ip));
uint16_t client_port = ntohs(client_addr.sin_port);
LOG_DEBUG("Thread %d processing %zu bytes from %s:%d",
thread_id, data.size(), client_ip, client_port);
// 模拟处理时间
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// 处理数据
std::string response = "Thread " + std::to_string(thread_id) +
" processed: " + std::string(data.begin(), data.end());
SendResponse(response.c_str(), response.length(), client_addr);
}
};
// 连接管理类
class ConnectionManager {
private:
struct ClientInfo {
sockaddr_in address;
time_t last_activity;
uint64_t packet_count;
uint64_t total_bytes;
ClientInfo(const sockaddr_in& addr)
: address(addr), last_activity(time(nullptr)),
packet_count(0), total_bytes(0) {}
};
std::unordered_map<std::string, ClientInfo> clients_;
std::mutex clients_mutex_;
time_t cleanup_interval_;
public:
ConnectionManager(time_t cleanup_interval = 300) // 5分钟
: cleanup_interval_(cleanup_interval) {}
// 更新客户端活动
void UpdateClient(const sockaddr_in& addr, size_t bytes) {
std::string key = GetClientKey(addr);
std::lock_guard<std::mutex> lock(clients_mutex_);
auto it = clients_.find(key);
if (it == clients_.end()) {
// 新客户端
clients_.emplace(key, ClientInfo(addr));
it = clients_.find(key);
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, ip_str, sizeof(ip_str));
LOG_INFO("New client connected: %s:%d",
ip_str, ntohs(addr.sin_port));
}
// 更新统计信息
it->second.last_activity = time(nullptr);
it->second.packet_count++;
it->second.total_bytes += bytes;
}
// 清理不活跃的连接
void CleanupInactiveClients() {
time_t now = time(nullptr);
std::vector<std::string> to_remove;
{
std::lock_guard<std::mutex> lock(clients_mutex_);
for (const auto& pair : clients_) {
if (now - pair.second.last_activity > cleanup_interval_) {
to_remove.push_back(pair.first);
}
}
for (const auto& key : to_remove) {
const auto& client = clients_[key];
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client.address.sin_addr,
ip_str, sizeof(ip_str));
LOG_INFO("Client %s:%d disconnected (inactive). "
"Packets: %lu, Bytes: %lu",
ip_str, ntohs(client.address.sin_port),
client.packet_count, client.total_bytes);
clients_.erase(key);
}
}
if (!to_remove.empty()) {
LOG_INFO("Cleaned up %zu inactive clients", to_remove.size());
}
}
// 获取客户端统计信息
std::string GetStats() const {
std::lock_guard<std::mutex> lock(clients_mutex_);
std::stringstream ss;
ss << "Active clients: " << clients_.size() << "\n";
for (const auto& pair : clients_) {
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &pair.second.address.sin_addr,
ip_str, sizeof(ip_str));
ss << ip_str << ":" << ntohs(pair.second.address.sin_port)
<< " - Packets: " << pair.second.packet_count
<< ", Bytes: " << pair.second.total_bytes
<< ", Last activity: "
<< (time(nullptr) - pair.second.last_activity)
<< " seconds ago\n";
}
return ss.str();
}
private:
std::string GetClientKey(const sockaddr_in& addr) const {
std::stringstream ss;
ss << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
return ss.str();
}
};
二、Main.cc实现
主程序负责初始化服务器并处理命令行参数。
cpp
#include <iostream>
#include <csignal>
#include <cstdlib>
#include <memory>
#include "UdpServer.hpp"
#include "AdvancedUdpServer.hpp"
// 全局服务器指针,用于信号处理
std::unique_ptr<UdpServer> g_server;
// 信号处理函数
void SignalHandler(int signal) {
std::cout << "\nReceived signal " << signal << ", shutting down..." << std::endl;
if (g_server) {
g_server->Stop();
}
}
// 显示使用帮助
void ShowUsage(const char* program_name) {
std::cout << "UDP Server v1.0\n\n";
std::cout << "Usage: " << program_name << " [options]\n\n";
std::cout << "Options:\n";
std::cout << " -p, --port PORT Server port (default: 8080)\n";
std::cout << " -b, --buffer SIZE Buffer size in bytes (default: 4096)\n";
std::cout << " -t, --timeout SEC Receive timeout in seconds (default: 5)\n";
std::cout << " -w, --workers NUM Number of worker threads (default: 1)\n";
std::cout << " -a, --advanced Use advanced server with thread pool\n";
std::cout << " -h, --help Show this help message\n";
std::cout << "\nExamples:\n";
std::cout << " " << program_name << " -p 9000 -b 8192\n";
std::cout << " " << program_name << " --port 8080 --workers 4 --advanced\n";
}
// 解析命令行参数
struct ServerConfig {
int port = 8080;
size_t buffer_size = 4096;
int timeout_sec = 5;
int timeout_usec = 0;
int workers = 1;
bool advanced = false;
bool reuse_addr = true;
};
ServerConfig ParseArguments(int argc, char* argv[]) {
ServerConfig config;
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-p" || arg == "--port") {
if (i + 1 < argc) {
config.port = std::atoi(argv[++i]);
if (config.port <= 0 || config.port > 65535) {
std::cerr << "Error: Port must be between 1 and 65535" << std::endl;
exit(1);
}
}
} else if (arg == "-b" || arg == "--buffer") {
if (i + 1 < argc) {
config.buffer_size = std::atoi(argv[++i]);
if (config.buffer_size < 1024 || config.buffer_size > 65536) {
std::cerr << "Error: Buffer size must be between 1024 and 65536" << std::endl;
exit(1);
}
}
} else if (arg == "-t" || arg == "--timeout") {
if (i + 1 < argc) {
config.timeout_sec = std::atoi(argv[++i]);
if (config.timeout_sec < 0) {
std::cerr << "Error: Timeout must be non-negative" << std::endl;
exit(1);
}
}
} else if (arg == "-w" || arg == "--workers") {
if (i + 1 < argc) {
config.workers = std::atoi(argv[++i]);
if (config.workers < 1 || config.workers > 32) {
std::cerr << "Error: Number of workers must be between 1 and 32" << std::endl;
exit(1);
}
}
} else if (arg == "-a" || arg == "--advanced") {
config.advanced = true;
} else if (arg == "-h" || arg == "--help") {
ShowUsage(argv[0]);
exit(0);
} else if (arg == "--no-reuse") {
config.reuse_addr = false;
} else {
std::cerr << "Error: Unknown option '" << arg << "'" << std::endl;
ShowUsage(argv[0]);
exit(1);
}
}
return config;
}
int main(int argc, char* argv[]) {
// 解析命令行参数
ServerConfig config = ParseArguments(argc, argv);
// 注册信号处理
signal(SIGINT, SignalHandler);
signal(SIGTERM, SignalHandler);
try {
std::cout << "=== UDP Server Starting ===\n";
std::cout << "Port: " << config.port << "\n";
std::cout << "Buffer size: " << config.buffer_size << " bytes\n";
std::cout << "Timeout: " << config.timeout_sec << " seconds\n";
std::cout << "Workers: " << config.workers << "\n";
std::cout << "Mode: " << (config.advanced ? "Advanced" : "Basic") << "\n";
std::cout << "===========================\n\n";
// 创建服务器实例
if (config.advanced) {
g_server = std::make_unique<AdvancedUdpServer>(config.port, config.workers);
} else {
g_server = std::make_unique<UdpServer>(config.port);
}
// 配置服务器
g_server->SetBufferSize(config.buffer_size);
g_server->SetTimeout(config.timeout_sec, config.timeout_usec);
g_server->SetReuseAddr(config.reuse_addr);
// 初始化服务器
if (!g_server->Init()) {
std::cerr << "Failed to initialize server" << std::endl;
return 1;
}
std::cout << "Server initialized successfully\n";
std::cout << "Press Ctrl+C to stop the server\n\n";
// 运行服务器
g_server->Run();
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
} catch (...) {
std::cerr << "Unknown exception occurred" << std::endl;
return 1;
}
std::cout << "\nServer stopped gracefully" << std::endl;
return 0;
}
// 性能测试函数
void RunPerformanceTest(int port) {
std::cout << "\n=== Performance Test ===\n";
// 创建测试服务器
auto test_server = std::make_unique<AdvancedUdpServer>(port, 4);
test_server->SetBufferSize(65536);
test_server->SetTimeout(1, 0);
if (!test_server->Init()) {
std::cerr << "Failed to initialize test server" << std::endl;
return;
}
// 在后台运行服务器
std::thread server_thread([&test_server]() {
test_server->Run();
});
// 给服务器时间启动
std::this_thread::sleep_for(std::chrono::seconds(1));
// 创建测试客户端
int client_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (client_sock < 0) {
std::cerr << "Failed to create test client socket" << std::endl;
return;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// 测试参数
const int NUM_PACKETS = 10000;
const int PACKET_SIZE = 1024;
std::vector<char> test_data(PACKET_SIZE, 'X');
auto start_time = std::chrono::high_resolution_clock::now();
// 发送测试数据包
for (int i = 0; i < NUM_PACKETS; i++) {
// 在数据中包含序列号
memcpy(test_data.data(), &i, sizeof(i));
ssize_t sent = sendto(client_sock, test_data.data(), PACKET_SIZE, 0,
(struct sockaddr*)&server_addr, sizeof(server_addr));
if (sent != PACKET_SIZE) {
std::cerr << "Failed to send packet " << i << std::endl;
break;
}
// 每1000个包打印进度
if ((i + 1) % 1000 == 0) {
std::cout << "Sent " << (i + 1) << " packets..." << std::endl;
}
// 小延迟避免拥塞
usleep(10);
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
close(client_sock);
// 停止服务器
test_server->Stop();
if (server_thread.joinable()) {
server_thread.join();
}
// 输出结果
std::cout << "\nPerformance Test Results:\n";
std::cout << "Packets sent: " << NUM_PACKETS << "\n";
std::cout << "Packet size: " << PACKET_SIZE << " bytes\n";
std::cout << "Total data: "
<< (NUM_PACKETS * PACKET_SIZE / 1024.0 / 1024.0)
<< " MB\n";
std::cout << "Total time: " << duration.count() << " ms\n";
std::cout << "Throughput: "
<< (NUM_PACKETS * PACKET_SIZE * 8.0 / duration.count() / 1000.0)
<< " Mbps\n";
std::cout << "Packets per second: "
<< (NUM_PACKETS * 1000.0 / duration.count())
<< "\n";
}
三、UDP客户端UdpClient.cc
3.1 基本框架设计
UDP客户端的设计需要简洁高效,支持多种操作模式。
cpp
#ifndef UDPCLIENT_H
#define UDPCLIENT_H
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <vector>
#include <chrono>
#include <thread>
#include <atomic>
#include <memory>
#include <iomanip>
class UdpClient {
private:
int sockfd_; // 套接字描述符
struct sockaddr_in server_addr_; // 服务器地址
std::string server_ip_; // 服务器IP地址
int server_port_; // 服务器端口
// 客户端状态
std::atomic<bool> is_connected_;
std::atomic<bool> is_running_;
// 统计信息
uint64_t packets_sent_;
uint64_t packets_received_;
uint64_t bytes_sent_;
uint64_t bytes_received_;
public:
// 构造函数
UdpClient(const std::string& ip = "127.0.0.1", int port = 8080);
// 析构函数
~UdpClient();
// 初始化客户端
bool Init();
// 连接服务器
bool Connect();
// 发送数据
bool Send(const std::string& data);
bool Send(const void* data, size_t len);
// 接收数据(阻塞)
bool Receive(std::string& data, int timeout_ms = 5000);
// 发送并等待响应
bool SendAndReceive(const std::string& send_data,
std::string& recv_data,
int timeout_ms = 5000);
// 运行交互模式
void RunInteractive();
// 运行性能测试模式
void RunPerformanceTest(int num_packets = 1000,
int packet_size = 1024);
// 获取统计信息
void GetStats(uint64_t& sent_packets, uint64_t& received_packets,
uint64_t& sent_bytes, uint64_t& received_bytes) const;
// 重置统计信息
void ResetStats();
// 断开连接
void Disconnect();
private:
// 创建套接字
bool CreateSocket();
// 设置套接字选项
bool SetSocketOptions();
// 打印状态
void PrintStatus() const;
// 显示帮助信息
void ShowHelp() const;
};
#endif // UDPCLIENT_H
3.2 创建套接字和连接
cpp
#include "UdpClient.h"
UdpClient::UdpClient(const std::string& ip, int port)
: server_ip_(ip), server_port_(port),
sockfd_(-1), is_connected_(false), is_running_(false),
packets_sent_(0), packets_received_(0),
bytes_sent_(0), bytes_received_(0) {
// 初始化服务器地址结构
memset(&server_addr_, 0, sizeof(server_addr_));
server_addr_.sin_family = AF_INET;
server_addr_.sin_port = htons(server_port_);
// 转换IP地址
if (inet_pton(AF_INET, server_ip_.c_str(), &server_addr_.sin_addr) <= 0) {
std::cerr << "Invalid IP address: " << server_ip_ << std::endl;
}
}
UdpClient::~UdpClient() {
Disconnect();
}
bool UdpClient::Init() {
// 创建套接字
if (!CreateSocket()) {
std::cerr << "Failed to create socket" << std::endl;
return false;
}
// 设置套接字选项
if (!SetSocketOptions()) {
std::cerr << "Failed to set socket options" << std::endl;
close(sockfd_);
return false;
}
std::cout << "UDP client initialized" << std::endl;
std::cout << "Server: " << server_ip_ << ":" << server_port_ << std::endl;
return true;
}
bool UdpClient::CreateSocket() {
// 创建UDP套接字
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd_ < 0) {
std::cerr << "Socket creation failed: " << strerror(errno) << std::endl;
return false;
}
std::cout << "Socket created successfully (fd: " << sockfd_ << ")" << std::endl;
return true;
}
bool UdpClient::SetSocketOptions() {
int optval = 1;
// 设置接收超时
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
if (setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
std::cerr << "Failed to set receive timeout: " << strerror(errno) << std::endl;
return false;
}
// 启用广播(如果需要)
optval = 1;
if (setsockopt(sockfd_, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) < 0) {
std::cerr << "Warning: Failed to enable broadcast: " << strerror(errno) << std::endl;
}
// 设置缓冲区大小
int buf_size = 1024 * 1024; // 1MB
if (setsockopt(sockfd_, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size)) < 0) {
std::cerr << "Warning: Failed to set receive buffer: " << strerror(errno) << std::endl;
}
if (setsockopt(sockfd_, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)) < 0) {
std::cerr << "Warning: Failed to set send buffer: " << strerror(errno) << std::endl;
}
return true;
}
bool UdpClient::Connect() {
if (sockfd_ < 0) {
std::cerr << "Socket not initialized" << std::endl;
return false;
}
// UDP是无连接的,这里只是测试与服务器的连通性
std::string test_msg = "CONNECT_TEST";
std::string response;
if (SendAndReceive(test_msg, response, 3000)) {
std::cout << "Successfully connected to server" << std::endl;
std::cout << "Server response: " << response << std::endl;
is_connected_ = true;
return true;
} else {
std::cerr << "Failed to connect to server" << std::endl;
return false;
}
}
3.3 发送和接收数据
cpp
bool UdpClient::Send(const std::string& data) {
return Send(data.c_str(), data.length());
}
bool UdpClient::Send(const void* data, size_t len) {
if (sockfd_ < 0) {
std::cerr << "Socket not initialized" << std::endl;
return false;
}
if (len == 0) {
std::cerr << "Attempting to send empty data" << std::endl;
return false;
}
// 检查数据包大小
if (len > 65507) {
std::cerr << "Data too large: " << len << " bytes (max: 65507)" << std::endl;
return false;
}
// 发送数据
ssize_t sent = sendto(sockfd_, data, len, 0,
(struct sockaddr*)&server_addr_,
sizeof(server_addr_));
if (sent < 0) {
std::cerr << "Send failed: " << strerror(errno) << std::endl;
return false;
}
if (static_cast<size_t>(sent) != len) {
std::cerr << "Partial send: " << sent << " of " << len << " bytes" << std::endl;
}
// 更新统计信息
packets_sent_++;
bytes_sent_ += sent;
std::cout << "Sent " << sent << " bytes to "
<< server_ip_ << ":" << server_port_ << std::endl;
return true;
}
bool UdpClient::Receive(std::string& data, int timeout_ms) {
if (sockfd_ < 0) {
std::cerr << "Socket not initialized" << std::endl;
return false;
}
// 设置接收超时
if (timeout_ms > 0) {
struct timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
// 接收缓冲区
char buffer[65536];
struct sockaddr_in from_addr;
socklen_t addr_len = sizeof(from_addr);
// 接收数据
ssize_t recv_len = recvfrom(sockfd_, buffer, sizeof(buffer) - 1, 0,
(struct sockaddr*)&from_addr, &addr_len);
if (recv_len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
std::cout << "Receive timeout" << std::endl;
} else {
std::cerr << "Receive failed: " << strerror(errno) << std::endl;
}
return false;
}
// 确保字符串以null结尾
buffer[recv_len] = '\0';
data.assign(buffer, recv_len);
// 获取发送者信息
char from_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &from_addr.sin_addr, from_ip, sizeof(from_ip));
uint16_t from_port = ntohs(from_addr.sin_port);
// 更新统计信息
packets_received_++;
bytes_received_ += recv_len;
std::cout << "Received " << recv_len << " bytes from "
<< from_ip << ":" << from_port << std::endl;
return true;
}
bool UdpClient::SendAndReceive(const std::string& send_data,
std::string& recv_data,
int timeout_ms) {
// 发送数据
if (!Send(send_data)) {
return false;
}
// 接收响应
if (!Receive(recv_data, timeout_ms)) {
return false;
}
return true;
}
3.4 交互模式和性能测试
cpp
void UdpClient::RunInteractive() {
if (!is_connected_) {
if (!Connect()) {
std::cerr << "Cannot start interactive mode: not connected" << std::endl;
return;
}
}
is_running_ = true;
std::cout << "\n=== UDP Client Interactive Mode ===\n";
std::cout << "Type 'help' for commands, 'quit' to exit\n\n";
std::string input;
while (is_running_) {
std::cout << "udp> ";
std::getline(std::cin, input);
if (input.empty()) {
continue;
}
// 处理命令
if (input == "quit" || input == "exit") {
std::cout << "Exiting..." << std::endl;
break;
} else if (input == "help") {
ShowHelp();
} else if (input == "status") {
PrintStatus();
} else if (input == "stats") {
std::cout << "\n=== Statistics ===\n";
std::cout << "Packets sent: " << packets_sent_ << "\n";
std::cout << "Packets received: " << packets_received_ << "\n";
std::cout << "Bytes sent: " << bytes_sent_ << "\n";
std::cout << "Bytes received: " << bytes_received_ << "\n";
if (packets_sent_ > 0) {
std::cout << "Average sent size: "
<< (bytes_sent_ / packets_sent_) << " bytes\n";
}
if (packets_received_ > 0) {
std::cout << "Average received size: "
<< (bytes_received_ / packets_received_) << " bytes\n";
}
} else if (input == "reset") {
ResetStats();
std::cout << "Statistics reset" << std::endl;
} else if (input == "ping") {
std::string response;
if (SendAndReceive("PING", response)) {
std::cout << "Server response: " << response << std::endl;
}
} else if (input == "time") {
std::string response;
if (SendAndReceive("TIME", response)) {
std::cout << "Server time: " << response;
}
} else if (input.compare(0, 4, "echo") == 0) {
if (input.length() > 5) {
std::string echo_data = input.substr(5);
std::string response;
if (SendAndReceive("ECHO " + echo_data, response)) {
std::cout << "Echo: " << response << std::endl;
}
} else {
std::cout << "Usage: echo <message>" << std::endl;
}
} else if (input.compare(0, 4, "file") == 0) {
// 模拟文件传输
std::string filename = input.length() > 5 ? input.substr(5) : "test.txt";
std::cout << "Simulating file transfer: " << filename << std::endl;
// 创建模拟文件内容
std::string file_content;
for (int i = 0; i < 100; i++) {
file_content += "Line " + std::to_string(i + 1) + ": This is test data\n";
}
// 分块发送
const size_t CHUNK_SIZE = 1024;
size_t total_sent = 0;
int chunk_num = 1;
for (size_t i = 0; i < file_content.length(); i += CHUNK_SIZE) {
size_t chunk_len = std::min(CHUNK_SIZE, file_content.length() - i);
std::string chunk = file_content.substr(i, chunk_len);
// 添加块头信息
std::string chunk_with_header = "FILE_CHUNK " +
std::to_string(chunk_num) + " " +
chunk;
if (Send(chunk_with_header)) {
total_sent += chunk_len;
std::cout << "Sent chunk " << chunk_num
<< " (" << chunk_len << " bytes)" << std::endl;
chunk_num++;
// 小延迟
usleep(10000); // 10ms
} else {
std::cerr << "Failed to send chunk " << chunk_num << std::endl;
break;
}
}
std::cout << "File transfer complete: " << total_sent << " bytes sent" << std::endl;
} else if (input == "perftest") {
RunPerformanceTest();
} else {
// 默认:发送原始消息
std::string response;
if (SendAndReceive(input, response)) {
std::cout << "Response: " << response << std::endl;
}
}
}
}
void UdpClient::RunPerformanceTest(int num_packets, int packet_size) {
std::cout << "\n=== Performance Test ===\n";
std::cout << "Packets: " << num_packets << "\n";
std::cout << "Packet size: " << packet_size << " bytes\n";
std::cout << "Total data: "
<< (num_packets * packet_size / 1024.0 / 1024.0)
<< " MB\n\n";
// 准备测试数据
std::vector<char> test_data(packet_size, 'X');
// 记录开始时间
auto start_time = std::chrono::high_resolution_clock::now();
// 发送测试数据包
int successful_sends = 0;
for (int i = 0; i < num_packets; i++) {
// 在数据中包含序列号和时间戳
uint64_t seq = i;
auto timestamp = std::chrono::high_resolution_clock::now();
uint64_t timestamp_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
timestamp.time_since_epoch()).count();
// 将序列号和时间戳复制到数据开始处
memcpy(test_data.data(), &seq, sizeof(seq));
memcpy(test_data.data() + sizeof(seq), ×tamp_ns, sizeof(timestamp_ns));
if (Send(test_data.data(), packet_size)) {
successful_sends++;
}
// 每100个包打印进度
if ((i + 1) % 100 == 0) {
std::cout << "Sent " << (i + 1) << " packets..." << std::endl;
}
// 控制发送速率(1000 packets/second)
usleep(1000);
}
// 记录结束时间
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
// 接收响应(可选)
std::cout << "\nWaiting for responses..." << std::endl;
int responses_received = 0;
auto receive_start = std::chrono::high_resolution_clock::now();
// 设置短超时接收剩余响应
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
char buffer[65536];
while (true) {
ssize_t recv_len = recvfrom(sockfd_, buffer, sizeof(buffer) - 1, 0, NULL, NULL);
if (recv_len > 0) {
responses_received++;
// 解析响应中的序列号
if (recv_len >= sizeof(uint64_t)) {
uint64_t seq;
memcpy(&seq, buffer, sizeof(seq));
// 可以在这里计算往返时间等
}
} else {
break; // 超时
}
}
auto receive_end = std::chrono::high_resolution_clock::now();
auto receive_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
receive_end - receive_start);
// 输出结果
std::cout << "\n=== Test Results ===\n";
std::cout << "Packets sent: " << successful_sends << "/" << num_packets << "\n";
std::cout << "Responses received: " << responses_received << "\n";
std::cout << "Send duration: " << duration.count() << " ms\n";
std::cout << "Receive duration: " << receive_duration.count() << " ms\n";
if (duration.count() > 0) {
double send_rate = (successful_sends * 1000.0) / duration.count();
double throughput = (successful_sends * packet_size * 8.0) /
(duration.count() * 1000.0); // Mbps
std::cout << "Send rate: " << send_rate << " packets/second\n";
std::cout << "Throughput: " << throughput << " Mbps\n";
}
if (responses_received > 0) {
double response_rate = (responses_received * 1000.0) / receive_duration.count();
std::cout << "Response rate: " << response_rate << " packets/second\n";
}
double loss_rate = 0;
if (successful_sends > 0) {
loss_rate = (1.0 - (responses_received / (double)successful_sends)) * 100.0;
std::cout << "Packet loss rate: " << std::fixed << std::setprecision(2)
<< loss_rate << "%\n";
}
}
void UdpClient::ShowHelp() const {
std::cout << "\nAvailable commands:\n";
std::cout << " help Show this help message\n";
std::cout << " quit, exit Exit the client\n";
std::cout << " status Show connection status\n";
std::cout << " stats Show statistics\n";
std::cout << " reset Reset statistics\n";
std::cout << " ping Send ping to server\n";
std::cout << " time Get server time\n";
std::cout << " echo <message> Echo message to server\n";
std::cout << " file [name] Simulate file transfer\n";
std::cout << " perftest Run performance test\n";
std::cout << " <any text> Send custom message\n";
}
void UdpClient::PrintStatus() const {
std::cout << "\n=== Client Status ===\n";
std::cout << "Server: " << server_ip_ << ":" << server_port_ << "\n";
std::cout << "Socket: " << (sockfd_ >= 0 ? "OK" : "Not initialized") << "\n";
std::cout << "Connected: " << (is_connected_ ? "Yes" : "No") << "\n";
std::cout << "Running: " << (is_running_ ? "Yes" : "No") << "\n";
}
void UdpClient::GetStats(uint64_t& sent_packets, uint64_t& received_packets,
uint64_t& sent_bytes, uint64_t& received_bytes) const {
sent_packets = packets_sent_;
received_packets = packets_received_;
sent_bytes = bytes_sent_;
received_bytes = bytes_received_;
}
void UdpClient::ResetStats() {
packets_sent_ = 0;
packets_received_ = 0;
bytes_sent_ = 0;
bytes_received_ = 0;
}
void UdpClient::Disconnect() {
if (sockfd_ >= 0) {
// 发送断开连接消息
std::string disconnect_msg = "DISCONNECT";
Send(disconnect_msg);
// 关闭套接字
close(sockfd_);
sockfd_ = -1;
std::cout << "Disconnected from server" << std::endl;
}
is_connected_ = false;
is_running_ = false;
}
// 客户端主程序
int main(int argc, char* argv[]) {
std::string server_ip = "127.0.0.1";
int server_port = 8080;
// 解析命令行参数
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-s" || arg == "--server") {
if (i + 1 < argc) {
server_ip = argv[++i];
}
} else if (arg == "-p" || arg == "--port") {
if (i + 1 < argc) {
server_port = std::atoi(argv[++i]);
}
} else if (arg == "-h" || arg == "--help") {
std::cout << "UDP Client Usage:\n";
std::cout << " -s, --server IP Server IP address (default: 127.0.0.1)\n";
std::cout << " -p, --port PORT Server port (default: 8080)\n";
std::cout << " -t, --test Run performance test\n";
std::cout << " -i, --interactive Run in interactive mode\n";
std::cout << " -h, --help Show this help\n";
return 0;
} else if (arg == "-t" || arg == "--test") {
// 性能测试模式
UdpClient client(server_ip, server_port);
if (client.Init() && client.Connect()) {
client.RunPerformanceTest();
}
return 0;
} else if (arg == "-i" || arg == "--interactive") {
// 交互模式(默认)
}
}
std::cout << "=== UDP Client ===\n";
std::cout << "Connecting to " << server_ip << ":" << server_port << "\n\n";
UdpClient client(server_ip, server_port);
if (!client.Init()) {
std::cerr << "Failed to initialize client" << std::endl;
return 1;
}
if (!client.Connect()) {
std::cerr << "Failed to connect to server" << std::endl;
return 1;
}
// 运行交互模式
client.RunInteractive();
std::cout << "\nClient terminated" << std::endl;
return 0;
}
四、测试
4.1 单元测试
cpp
// TestUdpServer.cpp
#include <gtest/gtest.h>
#include <thread>
#include <chrono>
#include "UdpServer.hpp"
#include "UdpClient.h"
class UdpServerTest : public ::testing::Test {
protected:
void SetUp() override {
// 启动测试服务器
test_port_ = 9999;
server_ = std::make_unique<UdpServer>(test_port_);
ASSERT_TRUE(server_->Init());
// 在后台线程运行服务器
server_thread_ = std::thread([this]() {
server_->Run();
});
// 等待服务器启动
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
void TearDown() override {
if (server_) {
server_->Stop();
}
if (server_thread_.joinable()) {
server_thread_.join();
}
}
int test_port_;
std::unique_ptr<UdpServer> server_;
std::thread server_thread_;
};
TEST_F(UdpServerTest, BasicEcho) {
UdpClient client("127.0.0.1", test_port_);
ASSERT_TRUE(client.Init());
std::string send_data = "Hello, Server!";
std::string recv_data;
EXPECT_TRUE(client.SendAndReceive(send_data, recv_data));
EXPECT_NE(recv_data.find("Server received"), std::string::npos);
EXPECT_NE(recv_data.find(send_data), std::string::npos);
}
TEST_F(UdpServerTest, MultipleClients) {
const int NUM_CLIENTS = 5;
std::vector<std::unique_ptr<UdpClient>> clients;
std::vector<std::thread> client_threads;
for (int i = 0; i < NUM_CLIENTS; i++) {
auto client = std::make_unique<UdpClient>("127.0.0.1", test_port_);
ASSERT_TRUE(client->Init());
clients.push_back(std::move(client));
}
// 并发发送消息
for (int i = 0; i < NUM_CLIENTS; i++) {
client_threads.emplace_back([&clients, i]() {
std::string send_data = "Message from client " + std::to_string(i);
std::string recv_data;
EXPECT_TRUE(clients[i]->SendAndReceive(send_data, recv_data, 3000));
});
}
// 等待所有线程完成
for (auto& thread : client_threads) {
thread.join();
}
}
TEST_F(UdpServerTest, LargeData) {
UdpClient client("127.0.0.1", test_port_);
ASSERT_TRUE(client.Init());
// 发送较大数据(小于64KB)
std::string large_data(50000, 'X'); // 50KB
std::string recv_data;
EXPECT_TRUE(client.SendAndReceive(large_data, recv_data, 5000));
EXPECT_GT(recv_data.size(), large_data.size());
}
TEST_F(UdpServerTest, Performance) {
UdpClient client("127.0.0.1", test_port_);
ASSERT_TRUE(client.Init());
const int NUM_PACKETS = 100;
const int PACKET_SIZE = 1400; // 避免分片
auto start_time = std::chrono::high_resolution_clock::now();
int success_count = 0;
for (int i = 0; i < NUM_PACKETS; i++) {
std::string data(PACKET_SIZE, 'A' + (i % 26));
std::string response;
if (client.SendAndReceive(data, response, 1000)) {
success_count++;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
std::cout << "\nPerformance Test Results:\n";
std::cout << "Successful exchanges: " << success_count << "/" << NUM_PACKETS << "\n";
std::cout << "Total time: " << duration.count() << " ms\n";
std::cout << "Average RTT: " << (duration.count() / (double)success_count) << " ms\n";
EXPECT_GT(success_count, NUM_PACKETS * 0.9); // 90%成功率
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
4.2 集成测试
cpp
// IntegrationTest.cpp
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include "AdvancedUdpServer.hpp"
#include "UdpClient.h"
class IntegrationTest {
private:
std::unique_ptr<AdvancedUdpServer> server_;
std::thread server_thread_;
int server_port_;
public:
IntegrationTest(int port = 8888, int workers = 4)
: server_port_(port) {
// 启动高性能服务器
server_ = std::make_unique<AdvancedUdpServer>(port, workers);
server_->SetBufferSize(65536);
server_->SetTimeout(1, 0);
if (!server_->Init()) {
throw std::runtime_error("Failed to initialize server");
}
// 在后台运行服务器
server_thread_ = std::thread([this]() {
server_->Run();
});
// 等待服务器启动
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "Test server started on port " << port << std::endl;
}
~IntegrationTest() {
if (server_) {
server_->Stop();
}
if (server_thread_.joinable()) {
server_thread_.join();
}
std::cout << "Test server stopped" << std::endl;
}
void RunLoadTest(int num_clients, int messages_per_client) {
std::cout << "\n=== Load Test ===\n";
std::cout << "Clients: " << num_clients << "\n";
std::cout << "Messages per client: " << messages_per_client << "\n";
std::vector<std::thread> client_threads;
std::atomic<int> total_success{0};
std::atomic<int> total_failures{0};
auto start_time = std::chrono::high_resolution_clock::now();
// 创建客户端线程
for (int client_id = 0; client_id < num_clients; client_id++) {
client_threads.emplace_back([this, client_id, messages_per_client,
&total_success, &total_failures]() {
UdpClient client("127.0.0.1", server_port_);
if (!client.Init()) {
total_failures += messages_per_client;
return;
}
int local_success = 0;
int local_failures = 0;
for (int msg_num = 0; msg_num < messages_per_client; msg_num++) {
std::string message = "Client" + std::to_string(client_id) +
"_Msg" + std::to_string(msg_num);
std::string response;
if (client.SendAndReceive(message, response, 1000)) {
local_success++;
// 验证响应包含原始消息
if (response.find(message) == std::string::npos) {
std::cerr << "Warning: Invalid response from server" << std::endl;
}
} else {
local_failures++;
}
// 小延迟避免拥塞
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
total_success += local_success;
total_failures += local_failures;
std::cout << "Client " << client_id << ": "
<< local_success << "/" << messages_per_client
<< " successful" << std::endl;
});
}
// 等待所有客户端完成
for (auto& thread : client_threads) {
thread.join();
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
// 输出结果
std::cout << "\n=== Load Test Results ===\n";
std::cout << "Total messages: " << (num_clients * messages_per_client) << "\n";
std::cout << "Successful: " << total_success << "\n";
std::cout << "Failed: " << total_failures << "\n";
std::cout << "Success rate: "
<< (total_success * 100.0 / (num_clients * messages_per_client))
<< "%\n";
std::cout << "Total time: " << duration.count() << " ms\n";
std::cout << "Throughput: "
<< (total_success * 1000.0 / duration.count())
<< " messages/second\n";
// 验证至少95%成功率
double success_rate = total_success * 100.0 / (num_clients * messages_per_client);
if (success_rate >= 95.0) {
std::cout << "\n✓ Load test PASSED" << std::endl;
} else {
std::cout << "\n✗ Load test FAILED (success rate below 95%)" << std::endl;
}
}
void RunStressTest() {
std::cout << "\n=== Stress Test ===\n";
const int NUM_CLIENTS = 10;
const int MESSAGES_PER_CLIENT = 1000;
const int MESSAGE_SIZE = 1000; // 1KB
std::vector<std::thread> client_threads;
std::atomic<uint64_t> total_bytes_sent{0};
std::atomic<uint64_t> total_bytes_received{0};
auto start_time = std::chrono::high_resolution_clock::now();
for (int i = 0; i < NUM_CLIENTS; i++) {
client_threads.emplace_back([this, i, &total_bytes_sent, &total_bytes_received]() {
UdpClient client("127.0.0.1", server_port_);
if (!client.Init()) {
return;
}
// 准备大消息
std::string large_message(MESSAGE_SIZE, 'A' + (i % 26));
uint64_t client_bytes_sent = 0;
uint64_t client_bytes_received = 0;
for (int j = 0; j < MESSAGES_PER_CLIENT; j++) {
// 修改消息内容
large_message[0] = '0' + (j % 10);
std::string response;
if (client.SendAndReceive(large_message, response, 500)) {
client_bytes_sent += large_message.size();
client_bytes_received += response.size();
}
// 更短的延迟以增加压力
if (j % 100 == 0) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
total_bytes_sent += client_bytes_sent;
total_bytes_received += client_bytes_received;
std::cout << "Stress client " << i << " completed" << std::endl;
});
}
for (auto& thread : client_threads) {
thread.join();
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
std::cout << "\n=== Stress Test Results ===\n";
std::cout << "Total data sent: "
<< (total_bytes_sent / 1024.0 / 1024.0) << " MB\n";
std::cout << "Total data received: "
<< (total_bytes_received / 1024.0 / 1024.0) << " MB\n";
std::cout << "Total time: " << duration.count() << " ms\n";
if (duration.count() > 0) {
double send_throughput = (total_bytes_sent * 8.0) /
(duration.count() * 1000.0); // Mbps
double receive_throughput = (total_bytes_received * 8.0) /
(duration.count() * 1000.0); // Mbps
std::cout << "Send throughput: " << send_throughput << " Mbps\n";
std::cout << "Receive throughput: " << receive_throughput << " Mbps\n";
std::cout << "Total throughput: " << (send_throughput + receive_throughput)
<< " Mbps\n";
}
if (total_bytes_sent > 0) {
std::cout << "\n✓ Stress test completed successfully" << std::endl;
}
}
};
int main() {
try {
std::cout << "=== UDP System Integration Test ===\n\n";
IntegrationTest test(8888, 4);
// 运行负载测试
test.RunLoadTest(5, 100);
// 运行压力测试
test.RunStressTest();
std::cout << "\n=== All tests completed ===\n";
} catch (const std::exception& e) {
std::cerr << "Test failed: " << e.what() << std::endl;
return 1;
}
return 0;
}
4.3 网络测试工具
cpp
// NetworkTestTool.cpp
#include <iostream>
#include <iomanip>
#include <vector>
#include <map>
#include <cmath>
#include "UdpClient.h"
class NetworkTestTool {
private:
std::string server_ip_;
int server_port_;
public:
NetworkTestTool(const std::string& ip, int port)
: server_ip_(ip), server_port_(port) {}
void RunLatencyTest(int num_packets = 100) {
std::cout << "\n=== Latency Test ===\n";
std::cout << "Server: " << server_ip_ << ":" << server_port_ << "\n";
std::cout << "Packets: " << num_packets << "\n\n";
UdpClient client(server_ip_, server_port_);
if (!client.Init()) {
std::cerr << "Failed to initialize client" << std::endl;
return;
}
std::vector<double> latencies;
int successful_packets = 0;
for (int i = 0; i < num_packets; i++) {
// 准备包含时间戳的消息
auto send_time = std::chrono::high_resolution_clock::now();
uint64_t send_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
send_time.time_since_epoch()).count();
std::string message = "PING_" + std::to_string(i) + "_" +
std::to_string(send_ns);
std::string response;
if (client.SendAndReceive(message, response, 1000)) {
auto recv_time = std::chrono::high_resolution_clock::now();
uint64_t recv_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
recv_time.time_since_epoch()).count();
// 计算往返时间
double rtt_ns = static_cast<double>(recv_ns - send_ns);
double rtt_ms = rtt_ns / 1000000.0;
latencies.push_back(rtt_ms);
successful_packets++;
if ((i + 1) % 10 == 0) {
std::cout << "Sent " << (i + 1) << " packets..." << std::endl;
}
} else {
std::cout << "Packet " << i << " lost" << std::endl;
}
// 等待以避免拥塞
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// 计算统计信息
if (!latencies.empty()) {
double sum = 0;
double min_latency = latencies[0];
double max_latency = latencies[0];
for (double latency : latencies) {
sum += latency;
min_latency = std::min(min_latency, latency);
max_latency = std::max(max_latency, latency);
}
double average = sum / latencies.size();
// 计算标准差
double variance = 0;
for (double latency : latencies) {
variance += (latency - average) * (latency - average);
}
variance /= latencies.size();
double stddev = std::sqrt(variance);
// 计算百分位数
std::sort(latencies.begin(), latencies.end());
double p50 = latencies[latencies.size() * 0.5];
double p90 = latencies[latencies.size() * 0.9];
double p95 = latencies[latencies.size() * 0.95];
double p99 = latencies[latencies.size() * 0.99];
// 输出结果
std::cout << "\n=== Latency Test Results ===\n";
std::cout << "Packets sent: " << num_packets << "\n";
std::cout << "Packets received: " << successful_packets << "\n";
std::cout << "Packet loss: "
<< std::fixed << std::setprecision(2)
<< ((num_packets - successful_packets) * 100.0 / num_packets)
<< "%\n";
std::cout << "\nLatency statistics (ms):\n";
std::cout << " Minimum: " << std::fixed << std::setprecision(3)
<< min_latency << "\n";
std::cout << " Maximum: " << max_latency << "\n";
std::cout << " Average: " << average << "\n";
std::cout << " Std Dev: " << stddev << "\n";
std::cout << " 50th percentile: " << p50 << "\n";
std::cout << " 90th percentile: " << p90 << "\n";
std::cout << " 95th percentile: " << p95 << "\n";
std::cout << " 99th percentile: " << p99 << "\n";
// 显示直方图
DisplayHistogram(latencies);
}
}
void DisplayHistogram(const std::vector<double>& data) {
if (data.empty()) return;
double min_val = *std::min_element(data.begin(), data.end());
double max_val = *std::max_element(data.begin(), data.end());
const int NUM_BINS = 10;
double bin_width = (max_val - min_val) / NUM_BINS;
std::vector<int> bins(NUM_BINS, 0);
for (double value : data) {
int bin_index = static_cast<int>((value - min_val) / bin_width);
if (bin_index == NUM_BINS) bin_index--; // 处理边界情况
bins[bin_index]++;
}
std::cout << "\nLatency distribution:\n";
for (int i = 0; i < NUM_BINS; i++) {
double bin_start = min_val + i * bin_width;
double bin_end = bin_start + bin_width;
std::cout << std::fixed << std::setprecision(1)
<< " " << std::setw(6) << bin_start
<< " - " << std::setw(6) << bin_end << " ms: "
<< std::string(bins[i] * 50 / data.size(), '#')
<< " (" << bins[i] << ")\n";
}
}
void RunBandwidthTest(int duration_sec = 10, int packet_size = 1400) {
std::cout << "\n=== Bandwidth Test ===\n";
std::cout << "Duration: " << duration_sec << " seconds\n";
std::cout << "Packet size: " << packet_size << " bytes\n\n";
UdpClient client(server_ip_, server_port_);
if (!client.Init()) {
std::cerr << "Failed to initialize client" << std::endl;
return;
}
std::vector<char> packet_data(packet_size, 'B');
auto start_time = std::chrono::steady_clock::now();
auto end_time = start_time + std::chrono::seconds(duration_sec);
uint64_t total_packets = 0;
uint64_t total_bytes = 0;
uint64_t successful_responses = 0;
std::cout << "Testing bandwidth...\n";
while (std::chrono::steady_clock::now() < end_time) {
// 发送数据包
if (client.Send(packet_data.data(), packet_size)) {
total_packets++;
total_bytes += packet_size;
}
// 尝试接收响应(非阻塞)
std::string response;
if (client.Receive(response, 10)) { // 10ms超时
successful_responses++;
}
// 控制发送速率(约1000 packets/second)
std::this_thread::sleep_for(std::chrono::microseconds(900));
}
auto actual_end = std::chrono::steady_clock::now();
auto actual_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
actual_end - start_time);
// 输出结果
std::cout << "\n=== Bandwidth Test Results ===\n";
std::cout << "Actual duration: " << actual_duration.count() << " ms\n";
std::cout << "Packets sent: " << total_packets << "\n";
std::cout << "Total data sent: "
<< (total_bytes / 1024.0 / 1024.0) << " MB\n";
std::cout << "Responses received: " << successful_responses << "\n";
if (actual_duration.count() > 0) {
double packets_per_sec = total_packets * 1000.0 / actual_duration.count();
double bandwidth_mbps = (total_bytes * 8.0) /
(actual_duration.count() * 1000.0);
std::cout << "Send rate: " << packets_per_sec << " packets/second\n";
std::cout << "Bandwidth: " << bandwidth_mbps << " Mbps\n";
std::cout << "Response rate: "
<< (successful_responses * 1000.0 / actual_duration.count())
<< " packets/second\n";
}
double loss_rate = 0;
if (total_packets > 0) {
loss_rate = (1.0 - (successful_responses / (double)total_packets)) * 100.0;
std::cout << "Estimated loss rate: "
<< std::fixed << std::setprecision(2) << loss_rate << "%\n";
}
}
};
int main(int argc, char* argv[]) {
std::string server_ip = "127.0.0.1";
int server_port = 8080;
std::string test_type = "latency";
// 解析命令行参数
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-s" || arg == "--server") {
if (i + 1 < argc) server_ip = argv[++i];
} else if (arg == "-p" || arg == "--port") {
if (i + 1 < argc) server_port = std::atoi(argv[++i]);
} else if (arg == "-t" || arg == "--test") {
if (i + 1 < argc) test_type = argv[++i];
} else if (arg == "-h" || arg == "--help") {
std::cout << "Network Test Tool\n\n";
std::cout << "Usage: " << argv[0] << " [options]\n\n";
std::cout << "Options:\n";
std::cout << " -s, --server IP Server IP address\n";
std::cout << " -p, --port PORT Server port\n";
std::cout << " -t, --test TYPE Test type (latency|bandwidth)\n";
std::cout << " -h, --help Show this help\n";
return 0;
}
}
NetworkTestTool tester(server_ip, server_port);
if (test_type == "latency") {
tester.RunLatencyTest();
} else if (test_type == "bandwidth") {
tester.RunBandwidthTest();
} else {
std::cerr << "Unknown test type: " << test_type << std::endl;
std::cerr << "Available types: latency, bandwidth" << std::endl;
return 1;
}
return 0;
}
五、源代码
5.1 Log.hpp - 日志系统
cpp
#ifndef LOG_HPP
#define LOG_HPP
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <mutex>
#include <memory>
// 日志级别
enum class LogLevel {
DEBUG,
INFO,
WARN,
ERROR,
FATAL
};
class Logger {
private:
static std::shared_ptr<Logger> instance_;
std::ofstream log_file_;
LogLevel min_level_;
std::mutex log_mutex_;
bool console_output_;
// 私有构造函数
Logger() : min_level_(LogLevel::INFO), console_output_(true) {}
public:
// 删除拷贝构造函数和赋值运算符
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
// 获取单例实例
static Logger& Instance() {
static std::shared_ptr<Logger> instance(new Logger());
return *instance;
}
// 初始化日志系统
bool Init(const std::string& filename = "",
LogLevel min_level = LogLevel::INFO,
bool console = true) {
std::lock_guard<std::mutex> lock(log_mutex_);
min_level_ = min_level;
console_output_ = console;
if (!filename.empty()) {
log_file_.open(filename, std::ios::app);
if (!log_file_.is_open()) {
std::cerr << "Failed to open log file: " << filename << std::endl;
return false;
}
}
return true;
}
// 设置日志级别
void SetLevel(LogLevel level) {
std::lock_guard<std::mutex> lock(log_mutex_);
min_level_ = level;
}
// 启用/禁用控制台输出
void EnableConsole(bool enable) {
std::lock_guard<std::mutex> lock(log_mutex_);
console_output_ = enable;
}
// 记录日志
void Log(LogLevel level, const std::string& message,
const char* file = nullptr, int line = 0) {
if (level < min_level_) {
return;
}
std::lock_guard<std::mutex> lock(log_mutex_);
// 获取当前时间
auto now = std::chrono::system_clock::now();
auto now_time = std::chrono::system_clock::to_time_t(now);
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) % 1000;
// 格式化时间
std::tm* tm_info = std::localtime(&now_time);
char time_buffer[80];
std::strftime(time_buffer, sizeof(time_buffer),
"%Y-%m-%d %H:%M:%S", tm_info);
// 日志级别字符串
const char* level_str = "";
switch (level) {
case LogLevel::DEBUG: level_str = "DEBUG"; break;
case LogLevel::INFO: level_str = "INFO"; break;
case LogLevel::WARN: level_str = "WARN"; break;
case LogLevel::ERROR: level_str = "ERROR"; break;
case LogLevel::FATAL: level_str = "FATAL"; break;
}
// 构建日志消息
std::stringstream ss;
ss << "[" << time_buffer << "."
<< std::setfill('0') << std::setw(3) << now_ms.count() << "] "
<< "[" << level_str << "] ";
if (file != nullptr) {
ss << "[" << file << ":" << line << "] ";
}
ss << message;
std::string log_message = ss.str();
// 输出到控制台
if (console_output_) {
std::ostream& stream = (level >= LogLevel::WARN) ? std::cerr : std::cout;
stream << log_message << std::endl;
if (level == LogLevel::FATAL) {
stream << "Fatal error, terminating..." << std::endl;
}
}
// 输出到文件
if (log_file_.is_open()) {
log_file_ << log_message << std::endl;
log_file_.flush();
if (level == LogLevel::FATAL) {
log_file_ << "Fatal error, terminating..." << std::endl;
log_file_.flush();
}
}
// 如果是致命错误,终止程序
if (level == LogLevel::FATAL) {
std::exit(EXIT_FAILURE);
}
}
// 关闭日志
void Close() {
std::lock_guard<std::mutex> lock(log_mutex_);
if (log_file_.is_open()) {
log_file_.close();
}
}
~Logger() {
Close();
}
};
// 日志宏
#define LOG_DEBUG(msg) Logger::Instance().Log(LogLevel::DEBUG, msg, __FILE__, __LINE__)
#define LOG_INFO(msg) Logger::Instance().Log(LogLevel::INFO, msg, __FILE__, __LINE__)
#define LOG_WARN(msg) Logger::Instance().Log(LogLevel::WARN, msg, __FILE__, __LINE__)
#define LOG_ERROR(msg) Logger::Instance().Log(LogLevel::ERROR, msg, __FILE__, __LINE__)
#define LOG_FATAL(msg) Logger::Instance().Log(LogLevel::FATAL, msg, __FILE__, __LINE__)
#endif // LOG_HPP
5.2 Makefile - 构建系统
makefile
# Makefile for UDP Network System
# Compiler and flags
CXX = g++
CXXFLAGS = -std=c++11 -Wall -Wextra -O2 -pthread
DEBUG_FLAGS = -g -DDEBUG
RELEASE_FLAGS = -O3 -DNDEBUG
# Directories
SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin
INC_DIR = include
# Source files
SERVER_SRCS = $(SRC_DIR)/UdpServer.cpp $(SRC_DIR)/Main.cpp $(SRC_DIR)/Log.cpp
CLIENT_SRCS = $(SRC_DIR)/UdpClient.cpp
TEST_SRCS = $(SRC_DIR)/TestUdpServer.cpp
INTEGRATION_SRCS = $(SRC_DIR)/IntegrationTest.cpp
NETTEST_SRCS = $(SRC_DIR)/NetworkTestTool.cpp
# Object files
SERVER_OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SERVER_SRCS))
CLIENT_OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(CLIENT_SRCS))
TEST_OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(TEST_SRCS))
INTEGRATION_OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(INTEGRATION_SRCS))
NETTEST_OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(NETTEST_SRCS))
# Executables
SERVER_EXE = $(BIN_DIR)/udp_server
CLIENT_EXE = $(BIN_DIR)/udp_client
TEST_EXE = $(BIN_DIR)/test_server
INTEGRATION_EXE = $(BIN_DIR)/integration_test
NETTEST_EXE = $(BIN_DIR)/network_test
# Include paths
INCLUDES = -I$(INC_DIR)
# Libraries
LIBS = -lpthread
TEST_LIBS = $(LIBS) -lgtest -lgtest_main
# Default target
all: directories server client
# Create directories
directories:
@mkdir -p $(OBJ_DIR) $(BIN_DIR)
# Server build
server: $(SERVER_EXE)
$(SERVER_EXE): $(SERVER_OBJS)
$(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $@ $(LIBS)
# Client build
client: $(CLIENT_EXE)
$(CLIENT_EXE): $(CLIENT_OBJS)
$(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $@ $(LIBS)
# Test build
test: $(TEST_EXE)
$(TEST_EXE): $(TEST_OBJS)
$(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $@ $(TEST_LIBS)
# Integration test build
integration: $(INTEGRATION_EXE)
$(INTEGRATION_EXE): $(INTEGRATION_OBJS)
$(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $@ $(LIBS)
# Network test tool build
nettest: $(NETTEST_EXE)
$(NETTEST_EXE): $(NETTEST_OBJS)
$(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $@ $(LIBS)
# Compile source files
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
$(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
# Debug build
debug: CXXFLAGS += $(DEBUG_FLAGS)
debug: all
# Release build
release: CXXFLAGS += $(RELEASE_FLAGS)
release: all
# Static analysis with cppcheck
check:
cppcheck --enable=all --suppress=missingIncludeSystem $(SRC_DIR) $(INC_DIR)
# Run tests
run-test: test
$(TEST_EXE)
run-integration: integration
$(INTEGRATION_EXE)
# Clean build files
clean:
rm -rf $(OBJ_DIR) $(BIN_DIR)
rm -f *.log
# Install system-wide (requires root)
install: release
cp $(SERVER_EXE) /usr/local/bin/udp_server
cp $(CLIENT_EXE) /usr/local/bin/udp_client
chmod +x /usr/local/bin/udp_server /usr/local/bin/udp_client
# Uninstall
uninstall:
rm -f /usr/local/bin/udp_server /usr/local/bin/udp_client
# Run server
run-server: server
$(SERVER_EXE) -p 8080
# Run client
run-client: client
$(CLIENT_EXE) -s 127.0.0.1 -p 8080
# Run network test
run-nettest: nettest
$(NETTEST_EXE) -s 127.0.0.1 -p 8080 -t latency
# Generate documentation
doc:
doxygen Doxyfile
# Help
help:
@echo "Available targets:"
@echo " all - Build server and client (default)"
@echo " server - Build server only"
@echo " client - Build client only"
@echo " test - Build and run unit tests"
@echo " integration - Build integration tests"
@echo " nettest - Build network test tool"
@echo " debug - Build with debug flags"
@echo " release - Build with release flags"
@echo " check - Run static analysis"
@echo " run-test - Run unit tests"
@echo " run-integration - Run integration tests"
@echo " clean - Remove build files"
@echo " install - Install system-wide"
@echo " uninstall - Uninstall"
@echo " run-server - Run server on port 8080"
@echo " run-client - Run client connecting to localhost:8080"
@echo " run-nettest - Run network latency test"
@echo " doc - Generate documentation"
@echo " help - Show this help"
.PHONY: all directories server client test integration nettest debug release \
check run-test run-integration clean install uninstall run-server \
run-client run-nettest doc help
5.3 完整的UdpServer.hpp
cpp
#ifndef UDPSERVER_HPP
#define UDPSERVER_HPP
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <thread>
#include <vector>
#include <memory>
#include <atomic>
#include <functional>
#include <queue>
#include <mutex>
#include <condition_variable>
#include "Log.hpp"
class UdpServer {
protected:
int port_; // 服务器端口
int sockfd_; // 套接字描述符
std::atomic<bool> is_running_; // 服务器运行状态
struct sockaddr_in server_addr_; // 服务器地址结构
struct sockaddr_in client_addr_; // 客户端地址结构
socklen_t client_addr_len_; // 客户端地址长度
// 服务器配置参数
size_t buffer_size_; // 缓冲区大小
int timeout_sec_; // 接收超时时间(秒)
int timeout_usec_; // 接收超时时间(微秒)
bool reuse_addr_; // 是否重用地址
public:
// 构造函数
explicit UdpServer(int port = 8080);
// 析构函数
virtual ~UdpServer();
// 禁止拷贝构造和赋值
UdpServer(const UdpServer&) = delete;
UdpServer& operator=(const UdpServer&) = delete;
// 初始化服务器
virtual bool Init();
// 运行服务器
virtual void Run();
// 停止服务器
virtual void Stop();
// 设置配置参数
void SetBufferSize(size_t size) { buffer_size_ = size; }
void SetTimeout(int sec, int usec = 0) {
timeout_sec_ = sec;
timeout_usec_ = usec;
}
void SetReuseAddr(bool reuse) { reuse_addr_ = reuse; }
// 获取服务器信息
int GetPort() const { return port_; }
bool IsRunning() const { return is_running_; }
protected:
// 创建套接字
virtual bool CreateSocket();
// 绑定地址
virtual bool BindAddress();
// 设置套接字选项
virtual bool SetSocketOptions();
// 处理接收到的数据
virtual void ProcessData(const char* data, ssize_t len,
const struct sockaddr_in& client_addr);
// 发送响应
virtual bool SendResponse(const char* data, ssize_t len,
const struct sockaddr_in& client_addr);
// 清理资源
virtual void Cleanup();
};
// 高级UDP服务器(带线程池)
class AdvancedUdpServer : public UdpServer {
private:
std::vector<std::thread> worker_threads_;
std::atomic<int> thread_count_;
int max_workers_;
// 线程池和工作队列
struct Task {
std::vector<char> data;
struct sockaddr_in client_addr;
time_t receive_time;
Task(const std::vector<char>& d, const struct sockaddr_in& addr)
: data(d), client_addr(addr), receive_time(time(nullptr)) {}
};
std::queue<Task> task_queue_;
std::mutex queue_mutex_;
std::condition_variable queue_cv_;
std::atomic<bool> workers_running_;
public:
AdvancedUdpServer(int port = 8080, int max_workers = 4);
~AdvancedUdpServer() override;
bool Init() override;
void Run() override;
void Stop() override;
// 获取线程池状态
int GetActiveWorkers() const { return thread_count_; }
size_t GetQueueSize() const { return task_queue_.size(); }
private:
void WorkerThread(int thread_id);
void ProcessTask(const Task& task, int thread_id);
// 重写基类方法
bool SetSocketOptions() override;
void ProcessData(const char* data, ssize_t len,
const struct sockaddr_in& client_addr) override;
// 任务调度
void AddTask(const std::vector<char>& data,
const struct sockaddr_in& client_addr);
};
#endif // UDPSERVER_HPP
5.4 完整的Main.cpp
cpp
#include <iostream>
#include <csignal>
#include <cstdlib>
#include <memory>
#include <getopt.h>
#include "UdpServer.hpp"
#include "AdvancedUdpServer.hpp"
// 全局服务器指针,用于信号处理
std::unique_ptr<UdpServer> g_server;
// 信号处理函数
void SignalHandler(int signal) {
std::cout << "\nReceived signal " << signal << ", shutting down..." << std::endl;
if (g_server) {
g_server->Stop();
}
}
// 显示使用帮助
void ShowUsage(const char* program_name) {
std::cout << "UDP Server v1.0 - High Performance UDP Server Implementation\n";
std::cout << "Build Date: " << __DATE__ << " " << __TIME__ << "\n\n";
std::cout << "Usage: " << program_name << " [options]\n\n";
std::cout << "Options:\n";
std::cout << " -p, --port PORT Server port (default: 8080)\n";
std::cout << " -b, --buffer SIZE Buffer size in bytes (default: 4096)\n";
std::cout << " -t, --timeout SEC Receive timeout in seconds (default: 5)\n";
std::cout << " -w, --workers NUM Number of worker threads (default: 1)\n";
std::cout << " -a, --advanced Use advanced server with thread pool\n";
std::cout << " -r, --no-reuse Disable address reuse\n";
std::cout << " -v, --verbose Enable verbose logging\n";
std::cout << " -d, --daemon Run as daemon\n";
std::cout << " -c, --config FILE Load configuration from file\n";
std::cout << " -h, --help Show this help message\n";
std::cout << "\nExamples:\n";
std::cout << " " << program_name << " -p 9000 -b 8192\n";
std::cout << " " << program_name << " --port 8080 --workers 4 --advanced\n";
std::cout << " " << program_name << " --daemon --config /etc/udp-server.conf\n";
}
// 服务器配置结构
struct ServerConfig {
int port = 8080;
size_t buffer_size = 4096;
int timeout_sec = 5;
int timeout_usec = 0;
int workers = 1;
bool advanced = false;
bool reuse_addr = true;
bool daemon = false;
bool verbose = false;
std::string config_file;
std::string log_file = "udp_server.log";
LogLevel log_level = LogLevel::INFO;
};
// 解析命令行参数
ServerConfig ParseArguments(int argc, char* argv[]) {
ServerConfig config;
struct option long_options[] = {
{"port", required_argument, 0, 'p'},
{"buffer", required_argument, 0, 'b'},
{"timeout", required_argument, 0, 't'},
{"workers", required_argument, 0, 'w'},
{"advanced", no_argument, 0, 'a'},
{"no-reuse", no_argument, 0, 'r'},
{"verbose", no_argument, 0, 'v'},
{"daemon", no_argument, 0, 'd'},
{"config", required_argument, 0, 'c'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "p:b:t:w:arvdc:h",
long_options, &option_index)) != -1) {
switch (opt) {
case 'p':
config.port = std::atoi(optarg);
if (config.port <= 0 || config.port > 65535) {
std::cerr << "Error: Port must be between 1 and 65535" << std::endl;
exit(EXIT_FAILURE);
}
break;
case 'b':
config.buffer_size = std::atoi(optarg);
if (config.buffer_size < 1024 || config.buffer_size > 65536) {
std::cerr << "Error: Buffer size must be between 1024 and 65536" << std::endl;
exit(EXIT_FAILURE);
}
break;
case 't':
config.timeout_sec = std::atoi(optarg);
if (config.timeout_sec < 0) {
std::cerr << "Error: Timeout must be non-negative" << std::endl;
exit(EXIT_FAILURE);
}
break;
case 'w':
config.workers = std::atoi(optarg);
if (config.workers < 1 || config.workers > 32) {
std::cerr << "Error: Number of workers must be between 1 and 32" << std::endl;
exit(EXIT_FAILURE);
}
break;
case 'a':
config.advanced = true;
break;
case 'r':
config.reuse_addr = false;
break;
case 'v':
config.verbose = true;
config.log_level = LogLevel::DEBUG;
break;
case 'd':
config.daemon = true;
break;
case 'c':
config.config_file = optarg;
// 这里可以添加从配置文件加载配置的逻辑
break;
case 'h':
ShowUsage(argv[0]);
exit(EXIT_SUCCESS);
default:
std::cerr << "Error: Unknown option" << std::endl;
ShowUsage(argv[0]);
exit(EXIT_FAILURE);
}
}
return config;
}
// 守护进程化
void Daemonize() {
pid_t pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork daemon: " << strerror(errno) << std::endl;
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程退出
exit(EXIT_SUCCESS);
}
// 子进程继续
umask(0);
pid_t sid = setsid();
if (sid < 0) {
std::cerr << "Failed to create new session: " << strerror(errno) << std::endl;
exit(EXIT_FAILURE);
}
if ((chdir("/")) < 0) {
std::cerr << "Failed to change directory: " << strerror(errno) << std::endl;
exit(EXIT_FAILURE);
}
// 关闭标准文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 重定向到/dev/null
open("/dev/null", O_RDONLY);
open("/dev/null", O_WRONLY);
open("/dev/null", O_RDWR);
}
int main(int argc, char* argv[]) {
// 解析命令行参数
ServerConfig config = ParseArguments(argc, argv);
// 如果需要,转换为守护进程
if (config.daemon) {
Daemonize();
}
// 注册信号处理
signal(SIGINT, SignalHandler);
signal(SIGTERM, SignalHandler);
signal(SIGPIPE, SIG_IGN); // 忽略管道破裂信号
try {
if (!config.daemon) {
std::cout << "=== UDP Server Starting ===\n";
std::cout << "Version: 1.0\n";
std::cout << "Port: " << config.port << "\n";
std::cout << "Buffer size: " << config.buffer_size << " bytes\n";
std::cout << "Timeout: " << config.timeout_sec << " seconds\n";
std::cout << "Workers: " << config.workers << "\n";
std::cout << "Mode: " << (config.advanced ? "Advanced (Thread Pool)" : "Basic") << "\n";
std::cout << "Log file: " << config.log_file << "\n";
std::cout << "Log level: " << (config.verbose ? "DEBUG" : "INFO") << "\n";
std::cout << "===========================\n\n";
}
// 初始化日志系统
Logger::Instance().Init(config.log_file, config.log_level, !config.daemon);
LOG_INFO("UDP Server starting...");
LOG_INFO("Configuration: port=%d, buffer=%zu, timeout=%d, workers=%d, mode=%s",
config.port, config.buffer_size, config.timeout_sec,
config.workers, config.advanced ? "advanced" : "basic");
// 创建服务器实例
if (config.advanced) {
g_server = std::make_unique<AdvancedUdpServer>(config.port, config.workers);
} else {
g_server = std::make_unique<UdpServer>(config.port);
}
// 配置服务器
g_server->SetBufferSize(config.buffer_size);
g_server->SetTimeout(config.timeout_sec, config.timeout_usec);
g_server->SetReuseAddr(config.reuse_addr);
// 初始化服务器
if (!g_server->Init()) {
LOG_FATAL("Failed to initialize server");
return EXIT_FAILURE;
}
LOG_INFO("Server initialized successfully");
if (!config.daemon) {
std::cout << "Server initialized successfully\n";
std::cout << "Press Ctrl+C to stop the server\n\n";
}
// 运行服务器
g_server->Run();
} catch (const std::exception& e) {
LOG_ERROR("Exception: %s", e.what());
if (!config.daemon) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return EXIT_FAILURE;
} catch (...) {
LOG_ERROR("Unknown exception occurred");
if (!config.daemon) {
std::cerr << "Unknown exception occurred" << std::endl;
}
return EXIT_FAILURE;
}
LOG_INFO("Server stopped gracefully");
if (!config.daemon) {
std::cout << "\nServer stopped gracefully" << std::endl;
}
return EXIT_SUCCESS;
}
总结
通过本文的详细讲解和代码实现,我们完成了一个完整的UDP网络通信系统的设计与实现。这个系统具有以下特点和优势:
1. 系统架构特点
模块化设计:
- 服务器和客户端分离,职责明确
- 日志系统独立,便于维护和扩展
- 配置系统灵活,支持命令行和配置文件
高性能设计:
- 支持多线程处理,充分利用多核CPU
- 智能缓冲区管理,避免内存碎片
- 异步I/O操作,减少等待时间
可靠性保障:
- 完善的错误处理和异常恢复机制
- 连接状态监控和自动清理
- 详细的日志记录,便于问题排查
2. 关键技术点
套接字编程核心:
- 深入理解了
socket()、bind()、recvfrom()、sendto()等系统调用 - 掌握了地址转换函数如
inet_pton()、inet_ntop()的使用 - 理解了字节序转换的重要性
并发处理:
- 多线程编程的最佳实践
- 线程安全的队列实现
- 条件变量的正确使用
网络优化:
- 缓冲区大小的优化配置
- 超时机制的合理设置
- 数据包分片和重组处理
3. 实际应用价值
教育意义:
- 完整的网络编程教学示例
- 良好的编码规范和架构设计示范
- 详细的注释和文档说明
实用价值:
- 可直接用于实际项目的网络通信模块
- 提供了性能测试和监控工具
- 支持多种运行模式和配置选项
扩展性:
- 易于添加新的协议支持
- 支持插件式功能扩展
- 良好的接口设计,便于二次开发
4. 性能优化建议
服务器端优化:
- 使用epoll或kqueue等I/O多路复用技术处理更多并发连接
- 实现连接池减少连接建立开销
- 使用内存池技术减少内存分配开销
客户端优化:
- 实现请求合并,减少网络包数量
- 添加压缩支持,减少数据传输量
- 实现智能重传机制,提高可靠性
网络优化:
- 支持IPv6双栈
- 添加QUIC协议支持
- 实现流量控制和拥塞避免算法
5. 安全考虑
基础安全:
- 输入验证和边界检查
- 缓冲区溢出防护
- 资源限制和配额管理
高级安全:
- 支持TLS/DTLS加密传输
- 实现身份验证和授权机制
- 添加DoS攻击防护
6. 未来发展方向
功能增强:
- 添加Web管理界面
- 支持集群部署
- 实现负载均衡
性能提升:
- 支持RDMA高速网络
- 添加GPU加速支持
- 实现零拷贝技术
生态系统:
- 提供多种语言SDK
- 支持云原生部署
- 集成监控告警系统
通过本系统的实现,读者不仅能够掌握UDP网络编程的核心技术,还能够学习到软件工程中的良好实践,包括模块化设计、错误处理、性能优化、测试策略等。这个系统可以作为学习网络编程的绝佳范例,也可以作为实际项目的基础框架进行扩展和优化。
网络编程是一个既深又广的领域,本文只是抛砖引玉。希望读者能够在此基础上继续探索,深入研究网络协议的各个层面,从应用层到底层实现,不断积累经验,最终成为网络编程的专家。