UDP的使用

代码:

udp_base.h

cpp 复制代码
#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

cpp 复制代码
#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

cpp 复制代码
#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

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

相关推荐
❥ღ Komo·20 分钟前
Ansible Playbook入门指南:核心语法与实战
网络
雪芽蓝域zzs1 小时前
uni-app 将 base64 图片编码转为 Blob 本地文件路径
网络协议·udp·uni-app
Σdoughty1 小时前
DNS主从服务
网络
CS_浮鱼2 小时前
【Linux】进程控制
linux·运维·网络
三川6983 小时前
1. 网络编程基础
开发语言·网络
Eloudy4 小时前
节点内 cuda GPU 之间 P2P IPC 通信的硬件机制参考
网络协议·p2p
atsec4 小时前
atsec完成Newland NPT的P2PE PA评估
服务器·网络协议·npt·p2pe
赖small强4 小时前
【ZeroRange WebRTC】UDP无序传输与丢包检测机制深度分析
udp·webrtc·rtp·抖动缓冲区·jitterbuffer
Guheyunyi4 小时前
安全风险监测系统核心技术
运维·网络·人工智能·安全
weixin_307779134 小时前
Amazon VPC中Web应用无法连接数据库的安全组配置问题分析与修复
网络·数据库·安全·云计算·aws