代码:
udp_base.h
#ifndef UDP_BASE_H
#define UDP_BASE_H
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>// IP地址转换函数
#include <system_error>
class UDPBase {
protected:
int sockfd; // socket文件描述符,-1表示无效
struct sockaddr_in address; // 网络地址结构体,存储IP和端口信息
bool is_initialized; // 标记socket是否已初始化
public:
UDPBase() : sockfd(-1), is_initialized(false) {} // 构造函数,初始化成员变量
virtual ~UDPBase() { // 虚析构函数,确保正确清理资源
if (sockfd != -1) { // 如果socket有效
close(sockfd); // 关闭socket
}
}
// 初始化socket
bool initialize() {
sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建UDP socket
// AF_INET: IPv4协议族
// SOCK_DGRAM: 数据报socket(UDP)
// 0: 默认协议
if (sockfd < 0) { // socket创建失败
std::cerr << "Socket creation failed: " << strerror(errno) << std::endl;
return false;
}
is_initialized = true; // 标记为已初始化
return true;
}
// 发送数据
bool sendData(const std::string& message, const struct sockaddr_in& dest_addr) {
if (!is_initialized) { // 检查socket是否初始化
std::cerr << "Socket not initialized" << std::endl;
return false;
}
// 发送数据到指定地址
ssize_t sent_bytes = sendto(sockfd, // socket描述符
message.c_str(), // 要发送的数据缓冲区
message.length(), // 数据长度
0, // 标志位(通常为0)
(const struct sockaddr*)&dest_addr, // 目标地址
sizeof(dest_addr)); // 地址结构体大小
if (sent_bytes < 0) {// 发送失败
std::cerr << "Send failed: " << strerror(errno) << std::endl;
return false;
}
std::cout << "Sent " << sent_bytes << " bytes" << std::endl;
return true;// 发送成功
}
// 接收数据
//struct sockaddr_in& sender_addr - 引用参数,用于输出发送方的地址信息
//int timeout_ms = 0 - 超时时间(毫秒),默认值0表示阻塞模式
std::string receiveData(struct sockaddr_in& sender_addr, int timeout_ms = 0) {
if (!is_initialized) {//检查socket是否已初始化
throw std::runtime_error("Socket not initialized");
}
// 设置超时(可选)
if (timeout_ms > 0) {
struct timeval tv;
tv.tv_sec = timeout_ms / 1000;// 计算秒数
tv.tv_usec = (timeout_ms % 1000) * 1000; // 计算微秒数
// 设置socket接收超时选项
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));//setsockopt: 设置socket选项,SO_RCVTIMEO表示接收超时
}
char buffer[1024];//创建1024字节的缓冲区用于存储接收到的数据
socklen_t addr_len = sizeof(sender_addr);//获取sender_addr结构体的大小
// ------------接收数据--------------
ssize_t recv_bytes = recvfrom(sockfd, // socket描述符
buffer, // 数据存储缓冲区
sizeof(buffer) - 1, // 最大接收字节数(留1字节给'\0')
0, // 标志位(通常为0)
(struct sockaddr*)&sender_addr, // 输出参数,接收发送方地址
&addr_len); // 输入时是缓冲区大小,输出时是实际地址长度
//错误处理
if (recv_bytes < 0) {//接收失败
if (errno == EAGAIN || errno == EWOULDBLOCK) {//超时错误(非阻塞模式或设置了超时)
throw std::runtime_error("Receive timeout");
}
throw std::runtime_error(std::string("Receive failed: ") + strerror(errno));
}
buffer[recv_bytes] = '\0';//最后一位添加\0
return std::string(buffer, recv_bytes);//构造std::string: 使用实际接收的字节数创建字符串
}
// 获取socket描述符
int getSocket() const { return sockfd; }
// 检查是否初始化
bool isInitialized() const { return is_initialized; }
};
#endif // UDP_BASE_H
udp_server.h
#ifndef UDP_SERVER_H
#define UDP_SERVER_H
#include "udp_base.h"
#include <functional>
//服务端需要长时间运行监听
//服务端必须绑定端口,通常不绑定IP(监听所有IP)
class UDPServer : public UDPBase {// 继承UDPBase
private:
int port;
//std::function智能的函数包装器
//const std::string& - 接收到的消息内容(常量引用,避免拷贝);struct sockaddr_in& - 客户端地址信息(引用,可修改)
std::function<void(const std::string&, struct sockaddr_in&)> message_callback;
public:
UDPServer(int port) : port(port) {} // 构造函数,初始化端口
// 设置消息回调函数
//std::function 是一个通用的函数包装器,它可以存储、复制和调用任何可调用对象
void setMessageCallback(std::function<void(const std::string&, struct sockaddr_in&)> callback) {
message_callback = callback;//将函数储存为一个对象(创建一个函数包装器,将用户提供的可调用对象存储在其中)
}
// 启动服务器
bool start() {
// 1. 初始化socket
if (!initialize()) {
return false;
}
// 2. 设置服务器地址结构
address.sin_family = AF_INET; // IPv4协议族
address.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口(0.0.0.0)
address.sin_port = htons(port); // 端口号(主机字节序转网络字节序)
// 3. 绑定端口
if (bind(sockfd, (struct sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "Bind failed: " << strerror(errno) << std::endl;
return false;
}
std::cout << "UDP Server started on port " << port << std::endl;
return true;
}
// 运行服务器(阻塞式)
void run() {
if (!is_initialized) {
throw std::runtime_error("Server not started");
}
while (true) {
try {
struct sockaddr_in client_addr;//局部引用地址,在receiveData被赋值
std::string message = receiveData(client_addr);//接收数据
std::cout << "Received from " << inet_ntoa(client_addr.sin_addr) //接收到数据的IP
<< ":" << ntohs(client_addr.sin_port) << " - " //接收到数据的端口
<< message << std::endl;
// 如果有回调函数,调用它
if (message_callback) {
message_callback(message, client_addr);// 调用用户自定义处理
} else {
// 默认响应
std::string response = "Echo: " + message;
sendData(response, client_addr);
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
}
// 发送响应
bool sendResponse(const std::string& response, const struct sockaddr_in& client_addr) {
return sendData(response, client_addr);
}
};
#endif // UDP_SERVER_H
udp_client.h
#ifndef UDP_CLIENT_H
#define UDP_CLIENT_H
#include "udp_base.h"
//客户端按需启动(发)
//客户端必须绑定IP(指向服务端),端口自动分配
class UDPClient : public UDPBase {// 继承UDPBase
private:
struct sockaddr_in server_addr; // 存储服务器地址信息
public:
// 构造函数:初始化服务器地址
UDPClient(const std::string& server_ip, int server_port) {
server_addr.sin_family = AF_INET; // IPv4协议族
server_addr.sin_port = htons(server_port);// 端口号(主机字节序转网络字节序)
// 将字符串IP地址转换为二进制格式
if (inet_pton(AF_INET, server_ip.c_str(), &server_addr.sin_addr) <= 0) {
throw std::runtime_error("Invalid server address");// IP地址格式错误
}
}
// 连接到服务器(UDP是无连接的,这里只是初始化)
bool connect() {
return initialize();// 调用基类的initialize()方法创建socket
}
// 发送消息到服务器
bool sendMessage(const std::string& message) {
if (!is_initialized) {
if (!connect()) {
return false;
}
}
return sendData(message, server_addr);//base 的发送数据
}
// 接收服务器响应
std::string receiveResponse(int timeout_ms = 5000) {// 默认5秒超时
if (!is_initialized) { // 检查socket是否已初始化
throw std::runtime_error("Client not connected");
}
struct sockaddr_in response_addr;// 用于存储响应来源地址(虽然不需要)
return receiveData(response_addr, timeout_ms);//接收数据
}
// 发送并等待响应
std::string sendAndReceive(const std::string& message, int timeout_ms = 5000) {
if (!sendMessage(message)) {// 先发送消息
throw std::runtime_error("Failed to send message");
}
return receiveResponse(timeout_ms);// 然后等待接收响应
}
};
#endif // UDP_CLIENT_H
main.cpp
#include "UDP/udp_base.h"
#include "UDP/udp_client.h"
#include "UDP/udp_server.h"
UDPServer udpserver(8080);//UDP通信端口
// 自定义消息处理回调函数
//const std::string& message: 接收到的消息内容(只读引用,避免拷贝)
//struct sockaddr_in& client_addr: 客户端地址信息(可修改引用)
void customMessageHandler(const std::string& message, struct sockaddr_in& client_addr) {
std::cout << "Custom handler processing: " << message << std::endl;
receive_message=message;
if(receive_message=="1")
{
std::string send_message="2";
udpserver.sendResponse(send_message,client_addr);
}
// 这里可以添加自定义处理逻辑
}
void runServer() {
// UDPServer udpserver(8080);//UDP通信端口
udpserver.setMessageCallback(customMessageHandler);//setMessageCallback将用户函数保存到成员变量中
if (udpserver.start()) {
udpserver.run(); // 这会阻塞,所以在单独线程中运行
}
}
void runClient() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "启动UDP客户端..." << std::endl;
UDPClient client("127.0.0.1", 8080); // 连接到本地服务器的8080端口
try {
std::cout << "客户端连接到: 127.0.0.1:8080" << std::endl;
std::string response = client.sendAndReceive("Hello Server!");
std::cout << "服务器响应: " << response << std::endl;
} catch (const std::exception& e) {
std::cerr << "客户端错误: " << e.what() << std::endl;
}
}
int main(int argc, char **argv)
{
// 在单独线程中运行udp服务器
std::thread server_thread(runServer);
NR_INFO("UDP Start success");
}
测试:
以串口工具作为客户端,以板卡程序作为服务端,监听客户端发送数据,若监听到客户端发送为1,则板卡服务端使用回调函数向串口工具发送2