当前位置: 首页 > java >正文

【Linux网络#3】:Socket编程应用层UDP

1.前言

UDP(User Datagram Protocol)是面向无连接的不可靠数据报传输协议,其特点如下:

  • 低开销 :无需建立连接(三次握手),直接发送数据
  • 实时性强 :适合音视频传输等对实时性要求高的场景
  • 数据独立 :每个数据报携带完整地址信息
  • 无流量控制 :可能导致数据丢失,但效率更高

典型应用场景:

  • DNS查询
  • DHCP协议
  • 多媒体流传输
  • 在线游戏数据同步

2.代码框架设计

项目结构

udp_project/
├── include/
│   ├── UdpServer.hpp     // 服务器核心类
│   ├── Common.hpp        // 公共定义
│   └── Log.hpp           // 日志系统
├── src/
│   ├── UdpServerMain.cc  // 服务器入口
│   └── UdpClientMain.cc  // 客户端入口
└── Makefile              // 编译脚本

3.基本实现 - EchoServer

UdpServer.hpp 核心实现

class UdpServer
{
public:UdpServer(const std::string &ip = gdefaultip, uint16_t port = gdefaultport): _sockfd(gsockfd), _ip(ip), _port(port), _isrunning(false) {}void InitServer(){// 创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) {LOG(FATAL) << "Socket creation failed: " << strerror(errno);Die(SOCKET_ERR);}LOG(INFO) << "Socket created successfully, FD: " << _sockfd;// 绑定地址sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());if (bind(_sockfd, CONV(&local), sizeof(local)) < 0) {LOG(FATAL) << "Binding failed: " << strerror(errno);Die(BIND_ERR);}LOG(INFO) << "Binding successful";}void Start(){_isrunning = true;while (true) {char inbuffer[1024];sockaddr_in peer;socklen_t len = sizeof(peer);// 接收数据ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer)-1, 0, CONV(&peer), &len);if (n > 0) {inbuffer[n] = '\0';LOG(DEBUG) << "Client says: " << inbuffer;// 构建响应std::string response = "Echo from server: ";response += inbuffer;// 发送响应sendto(_sockfd, response.c_str(), response.size(), 0,CONV(&peer), sizeof(peer));}}}private:int _sockfd;uint16_t _port;std::string _ip;bool _isrunning;
};

代码片段分析

InitServer() 方法详解
void InitServer()
{// 1. 创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0) {// 错误处理:使用日志系统记录错误信息LOG(LogLevel::FATAL) << "socket: " << strerror(errno);Die(SOCKET_ERR);}// 2. 填充地址结构struct sockaddr_in local;bzero(&local, sizeof(local)); // 清空结构体local.sin_family = AF_INET;  // IPv4协议local.sin_port = htons(_port); // 主机字节序转网络字节序local.sin_addr.s_addr = inet_addr(_ip.c_str()); // IP地址转换// 3. 绑定套接字int n = ::bind(_sockfd, CONV(&local), sizeof(local));if(n < 0) {LOG(LogLevel::FATAL) << "bind: " << strerror(errno);Die(BIND_ERR);}
}

关键点解析:

  1. socket()函数

    • AF_INET:IPv4协议族
    • SOCK_DGRAM:数据报套接字
    • protocol=0:自动选择UDP协议

    sockaddr_in结构体

struct sockaddr_in {sa_family_t    sin_family; // 地址族in_port_t      sin_port;   // 端口号struct in_addr sin_addr;   // IPv4地址char           sin_zero[8]; // 填充字段
};
  1. 地址转换函数

    • htons():主机字节序转网络字节序(16位)
    • htonl():32位版本
    • inet_addr():将点分IP转为32位网络序整数
Start() 方法详解
void Start()
{_isrunning = true;while(true) {char inbuffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 接收数据报ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer)-1, 0, CONV(&peer), &len);if(n > 0) {inbuffer[n] = '\0'; // 添加字符串结束符// 构建响应数据std::string echo_string = "echo# ";echo_string += inbuffer;// 发送响应sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0,CONV(&peer), sizeof(peer));}}
}
核心API解析:

1.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表示阻塞接收
  • src_addr:对端地址信息
  • addrlen:地址结构长度

2.sendto() :

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
  • dest_addr:目标地址
  • addrlen:地址长度

3.UdpClientMain.cc 客户端实现

#include "UdpClient.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"int main(int argc, char *argv[])
{if(argc != 3) {std::cerr << "Usage: " << argv[0] << " serverIp serverPort" << std::endl;Die(USAGE_ERR);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0) {std::cerr << "Socket creation failed: " << strerror(errno) << std::endl;Die(SOCKET_ERR);}// 填充服务器地址sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());// 通信循环while(true) {std::cout << "Client> ";std::string message;std::getline(std::cin, message);// 发送请求sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));// 接收响应char buffer[64];sockaddr_in tmp;socklen_t len = sizeof(tmp);ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, CONV(&tmp), &len);if(n > 0) {buffer[n] = '\0';std::cout << "Server> " << buffer << std::endl;}}return 0;
}

注意事项

  1. 客户端绑定机制

    • 不需要显式调用bind(),操作系统会自动分配端口
    • 首次调用sendto()时,OS会自动完成绑定
  2. 端口选择原则

    • 0-1023:系统端口(需root权限)
    • 1024-49151:注册端口
    • 49152-65535:动态/私有端口
    • 建议使用8080/8081等未被占用的端口

地址转换技巧

// 字符串IP转网络序整数
uint32_t ip = inet_addr("192.168.1.1");// 网络序整数转字符串
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &ip, ip_str, INET_ADDRSTRLEN);

4.UdpServer.hpp 修改 - 打印客户端信息

void Start()
{_isrunning = true;while (true) {char inbuffer[1024];sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer)-1, 0, CONV(&peer), &len);if (n > 0) {inbuffer[n] = '\0';// 获取客户端信息std::string client_ip = inet_ntoa(peer.sin_addr);uint16_t client_port = ntohs(peer.sin_port);LOG(DEBUG) << "Client[" << client_ip << ":" << client_port << "] says: " << inbuffer;// 构建响应std::string response = "Echo from server: ";response += inbuffer;sendto(_sockfd, response.c_str(), response.size(), 0,CONV(&peer), sizeof(peer));}}
}

5.通信验证(Linux-Linux)

启动服务器

# 编译服务器
g++ -o server UdpServerMain.cc -Iinclude# 运行服务器
./server 127.0.0.1 8080

启动客户端

# 编译客户端
g++ -o client UdpClientMain.cc -Iinclude# 运行客户端
./client 127.0.0.1 8080

交互示例

服务器端日志

[INFO] Socket created successfully, FD: 3
[INFO] Binding successful
[DEBUG] Client[127.0.0.1:34567] says: Hello Server
[DEBUG] Client[127.0.0.1:34567] says: UDP Test

客户端交互:

Client> Hello Server
Server> Echo from server: Hello ServerClient> UDP Test
Server> Echo from server: UDP Test

6.核心API总结

7.错误处理机制

错误码定义(Common.hpp)

enum ErrorType {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,// ...其他错误码
};

日志系统(Log.hpp)

namespace LogModule {enum LogLevel {DEBUG = 0,INFO,WARNING,ERROR,FATAL};
}

http://www.xdnf.cn/news/3847.html

相关文章:

  • Scartch038(四季变换)
  • MCP智能体多Agent协作系统设计(Multi-Agent Cooperation)
  • 模型部署——cuda编程入门
  • C语言内存函数详解:从基础到实战
  • 2025年渗透测试面试题总结-拷打题库38(题目+回答)
  • profile软件开发中的性能剖析与内存分析
  • 数据库Mysql_联合查询
  • Python----机器学习(模型评估:准确率、损失函数值、精确度、召回率、F1分数、混淆矩阵、ROC曲线和AUC值、Top-k精度)
  • 双列集合——map集合和三种遍历方式
  • React实现B站评论Demo
  • 分布式系统中的 ActiveMQ:异步解耦与流量削峰(一)
  • Dify 完全指南(一):从零搭建开源大模型应用平台(Ollama/VLLM本地模型接入实战)》
  • Github2025-05-04php开源项目日报 Top10
  • 详解迁移学习,模型参数冻结,优化器参数定义
  • 传感器数据处理笔记
  • Linux中的粘滞位和开发工具和文本编辑器vim
  • 马小帅面试遇“灵魂拷问“
  • hot100:链表倒数k个节点- 力扣(LeetCode)
  • 研0大模型学习(第11天)
  • FFT实现(Cooley-Tukey算法)
  • WEB 前端学 JAVA(二)Java 的发展与技术图谱简介
  • TS 字面量类型
  • Mybatis学习(下)
  • LabVIEW开发风量智能监测系统
  • 【杂谈】-探索 NVIDIA Dynamo 的高性能架构
  • 牛客周赛90 C题- Tk的构造数组 题解
  • STM32智能垃圾桶:四种控制模式实战开发
  • 58认知干货:创业经验分享及企业形式的汇总
  • 【AI面试准备】逻辑思维、严谨性、总结能力、沟通协作、适应力与目标导向
  • 文件一键解密软件工具(支持pdf、word、excel、ppt、rar、zip格式文件)