Unix 上的 UDP-Socket 编程
categories: 技术 tags: 软件
@
UDP 套接字编程基础知识
UDP(用户数据报协议)是一种无连接的、不可靠的、基于数据报的传输层通信协议。与 TCP 不同,UDP 不需要建立连接,直接发送数据报文。
套接字 API 的基本流程
-
创建套接字
int socket(int domain, int type, int protocol);domain: 协议域,如 AF_INET 表示 IPv4type: 套接字类型,如 SOCK_DGRAM 表示 UDPprotocol: 协议,通常为 0 表示默认
-
服务器端操作 服务器需要执行以下操作:
int bind(int socket, struct sockaddr* address, int addr_len); int recvfrom(int socket, char *buffer, int buf_len, int flags, struct sockaddr *address, int *addr_len); int sendto(int socket, char *message, int msg_len, int flags, struct sockaddr *address, int addr_len);bind: 将套接字绑定到指定地址和端口recvfrom: 接收数据报文并获取发送方地址sendto: 向指定地址发送数据报文
-
客户端操作 客户端不需要建立连接,可以直接发送数据:
int sendto(int socket, char *message, int msg_len, int flags, struct sockaddr *address, int addr_len); int recvfrom(int socket, char *buffer, int buf_len, int flags, struct sockaddr *address, int *addr_len);sendto: 向指定地址发送数据报文recvfrom: 接收数据报文并获取发送方地址
-
关闭套接字 最后需要关闭套接字以释放资源:
int close(int socket);环境准备
在 MacOS 系统下进行 UDP 套接字编程,需要使用 C++ 编译器(如 g++)和系统提供的网络编程头文件。主要包含以下头文件:
sys/socket.h:套接字相关函数netinet/in.h:网络地址结构定义arpa/inet.h:IP 地址转换函数unistd.h:系统调用函数stdio.h:标准输入输出函数string.h:字符串处理函数
服务器端代码
UDP 服务器端程序负责接收客户端发送的数据报文,并向客户端发送响应。
#include<iostream> #include<unistd.h> #include<stdio.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> using namespace std; #define SERVER_PORT 5051 //端口号 #define SERVER_IP "127.0.0.1" //服务器 ip #define BUFFER_SIZE 256 //缓冲区大小 int main(int argc, char *argv[]) { //创建一个 UDP 套接字 int sockSer; sockSer = socket(AF_INET, SOCK_DGRAM, 0); if(sockSer == -1){ perror("socket"); return -1; } //设置端口可以重用 int yes = 1; if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){ perror("setsockopt"); return -1; } //创建一个记录地址信息的结构体 struct sockaddr_in addrSer, addrCli; addrSer.sin_family = AF_INET; //使用 AF_INET 协议族 addrSer.sin_port = htons(SERVER_PORT); //设置端口号 addrSer.sin_addr.s_addr = inet_addr(SERVER_IP); //设置服务器 ip //将套接字地址与所创建的套接字号联系起来 socklen_t addrlen = sizeof(struct sockaddr); int res = ::bind(sockSer, (struct sockaddr*)&addrSer, addrlen); if(res == -1) { perror("bind"); return -1; } printf("UDP Server is running on %s:%d\n", SERVER_IP, SERVER_PORT); printf("Waiting for messages...\n"); char recvbuf[BUFFER_SIZE]; //接收缓冲区 char sendbuf[BUFFER_SIZE]; //发送缓冲区 while(1) { // 接收来自客户端的数据 memset(recvbuf, 0, sizeof(recvbuf)); socklen_t client_len = sizeof(addrCli); int recv_len = recvfrom(sockSer, recvbuf, sizeof(recvbuf)-1, 0, (struct sockaddr*)&addrCli, &client_len); if (recv_len > 0) { printf("Received from Client %s:%d: %s\n", inet_ntoa(addrCli.sin_addr), ntohs(addrCli.sin_port), recvbuf); // 准备响应数据 sprintf(sendbuf, "Server received: %s", recvbuf); // 发送响应给客户端 if (sendto(sockSer, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrCli, client_len) <= 0) { printf("发送响应失败\n"); } else { printf("Sent response to Client %s:%d\n", inet_ntoa(addrCli.sin_addr), ntohs(addrCli.sin_port)); } } else if (recv_len == 0) { printf("客户端已断开连接\n"); break; } else { perror("recvfrom"); break; } } close(sockSer); //关闭套接字 return 0; }客户端代码
UDP 客户端程序负责向服务器发送数据报文,并接收服务器的响应。
#include<iostream> #include<unistd.h> #include<stdio.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> using namespace std; #define SERVER_PORT 5051 //端口号 #define SERVER_IP "127.0.0.1" //服务器 ip #define BUFFER_SIZE 256 //缓冲区大小 int main(int argc, char *argv[]) { //创建一个 UDP 套接字 int sockCli; sockCli = socket(AF_INET, SOCK_DGRAM, 0); if(sockCli == -1){ perror("socket"); return -1; } //创建一个记录服务器地址信息的结构体 struct sockaddr_in addrSer; addrSer.sin_family = AF_INET; //使用 AF_INET 协议族 addrSer.sin_port = htons(SERVER_PORT); //设置端口号 addrSer.sin_addr.s_addr = inet_addr(SERVER_IP); //设置服务器 ip printf("UDP Client is ready\n"); printf("Enter 'quit' to exit\n"); char sendbuf[BUFFER_SIZE]; //发送缓冲区 char recvbuf[BUFFER_SIZE]; //接收缓冲区 while(1) { // 显示客户端提示符,等待用户输入 printf("Cli:>"); // 使用 fgets 读取整行输入,包括空格 if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL) { printf("读取输入失败\n"); break; } // 移除换行符 sendbuf[strcspn(sendbuf, "\n")] = 0; if(strcmp(sendbuf,"quit") == 0) //如果客户端发送的数据为"quit",则退出。 break; // 发送数据给服务器 socklen_t server_len = sizeof(addrSer); if (sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, server_len) <= 0) { printf("发送数据失败\n"); break; } // 接收来自服务器的响应 memset(recvbuf, 0, sizeof(recvbuf)); int recv_len = recvfrom(sockCli, recvbuf, sizeof(recvbuf)-1, 0, (struct sockaddr*)&addrSer, &server_len); if (recv_len > 0) { printf("Ser:> %s\n",recvbuf); } else if (recv_len == 0) { printf("服务器已断开连接\n"); break; } else { perror("recvfrom"); break; } } close(sockCli); //关闭套接字 return 0; }编译和运行
在 MacOS 系统下,可以使用以下命令编译这两个程序:
g++ -o UdpServer udpServer.cpp g++ -o UdpClient udpClient.cpp然后分别运行服务器和客户端程序:
./UdpServer ./UdpClientUDP 与 TCP 的区别
- 连接性:TCP 是面向连接的,需要先建立连接;UDP 是无连接的,直接发送数据报文。
- 可靠性:TCP 提供可靠的数据传输,保证数据顺序和完整性;UDP 不保证数据的可靠传输。
- 速度:UDP 比 TCP 更快,因为没有建立连接和确认的开销。
- 数据边界:UDP 保持数据报文的边界;TCP 将数据看作字节流,没有边界。
- 应用场景:TCP 适用于对数据可靠性要求高的场景,如文件传输、网页浏览等;UDP 适用于对实时性要求高、能容忍少量数据丢失的场景,如视频通话、在线游戏等。
代码特点说明
UDP 套接字编程与 TCP 套接字编程有以下主要区别:
- 套接字类型:UDP 使用 SOCK_DGRAM 类型,TCP 使用 SOCK_STREAM 类型。
- 连接处理:UDP 不需要建立连接,直接使用 sendto 和 recvfrom 函数发送和接收数据。
- 地址信息:UDP 在每次发送和接收数据时都需要指定或获取地址信息。
- 数据传输:UDP 保持数据报文的边界,每个 sendto 调用对应一个 recvfrom 接收。
通过以上代码示例,可以了解 UDP 套接字编程的基本流程和实现方式。