UDP的使用
代码:
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}}// 初始化socketbool 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';//最后一位添加\0return 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. 初始化socketif (!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