UDP的使用

发布于:2025-09-09 ⋅ 阅读:(24) ⋅ 点赞:(0)

代码:

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


网站公告

今日签到

点亮在社区的每一天
去签到