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 Server
- Client> 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
- };
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |