【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);}
}
关键点解析:
-
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]; // 填充字段
};
-
地址转换函数 :
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;
}
注意事项
-
客户端绑定机制 :
- 不需要显式调用
bind()
,操作系统会自动分配端口 - 首次调用
sendto()
时,OS会自动完成绑定
- 不需要显式调用
-
端口选择原则 :
- 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};
}