Linux UDP socket 通信示例
前面你已经了解了 UDP 协议的基本原理和特点。这一节我们就来实战一下,手动写出 UDP 的客户端和服务端程序,掌握 UDP 通信在 Linux 下的实际用法。
UDP 通信模型回顾
UDP 是无连接的,因此服务端不需要像 TCP 那样 listen()
和 accept()
,客户端也无需 connect()
,双方可以通过 sendto()
和 recvfrom()
实现互发数据。
UDP 是一种面向无连接的 Socket 通信方式,和 TCP 传输数据流不同,UDP 传输的是数据包。由于 UDP 不需要事先在通信双方之间建立连接,因此也弱化了客户端(Client)和服务端(Server)的概念,把它们称为接收端和发送端更为合适。
在下面的示例中,我们将创建两个 UDP 程序,暂时仍称为 UDP Server 和 UDP Client,它们的工作流程如下所示。
UDP 服务端(Server):
- 创建一个 UDP socket
- 将 socket 与本机的 IP 地址绑定
- 等待客户端发送数据包(datagram packet)
- 处理数据包并给客户端发送一个回复
- 回到第 3 步循环
UDP 客户端(Client):
- 创建一个 UDP socket.
- 给服务端发送一条消息
- 等待服务端的回复
- 处理回复后回到第 2 步(如果需要)
- 关闭 socket,退出程序
UDP 完整示例代码
UDP 服务端
你可以先创建一个简单的服务端程序,监听某个端口,接收客户端发来的消息:
udp_server.c
// udp_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 12345
#define BUF_SIZE 1024
int main() {
int sockfd;
char buffer[BUF_SIZE];
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定地址
if (bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("UDP 服务端已启动,监听端口 %d...\n", PORT);
while (1) {
int n = recvfrom(sockfd, buffer, BUF_SIZE - 1, 0,
(struct sockaddr *)&client_addr, &client_len);
buffer[n] = '\0';
printf("收到来自客户端的消息:%s\n", buffer);
// 回复客户端
sendto(sockfd, "收到消息", strlen("收到消息"), 0,
(struct sockaddr *)&client_addr, client_len);
}
close(sockfd);
return 0;
}
UDP 客户端
然后写一个简单的客户端,向服务端发送消息并接收回应:
udp_client.c
// udp_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 12345
#define BUF_SIZE 1024
int main() {
int sockfd;
char buffer[BUF_SIZE];
struct sockaddr_in server_addr;
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 发送消息
const char *msg = "你好,UDP 服务器!";
sendto(sockfd, msg, strlen(msg), 0,
(const struct sockaddr *)&server_addr, sizeof(server_addr));
// 接收回应
socklen_t len = sizeof(server_addr);
int n = recvfrom(sockfd, buffer, BUF_SIZE - 1, 0,
(struct sockaddr *)&server_addr, &len);
buffer[n] = '\0';
printf("收到服务器回应:%s\n", buffer);
close(sockfd);
return 0;
}
编译和运行
方式一:编写 Makefile 文件
all:
gcc udp_server.c -o udp_server
gcc udp_client.c -o udp_client
.PHONY: clean
clean:
rm udp_server udp_client
现在可以执行 make
命令编译代码
$ make
gcc udp_server.c -o udp_server
gcc udp_client.c -o udp_client
方式二:编写 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.0.2)
project(UDP_Example VERSION 1.0.0)
add_executable(udp_server udp_server.c)
add_executable(udp_client udp_client.c)
新建一个 build 目录,然后通过 cmake
和 make
命令编译代码
$ mkdir build
$ cd build
$ cmake ..
$ make
编译完成后,先运行 UDP 服务端(保持运行):
$ ./udp_server
再运行 UDP 客户端:
$ ./udp_client
你会看到服务端输出接收到的消息,客户端收到回复。
API 说明
小结
通过本文你学会了:
- 如何使用
socket()
、bind()
、sendto()
和recvfrom()
创建 UDP 服务端与客户端。 - UDP 的通信流程不需要建立连接,适合快速、一对多、轻量的通信场景。
- 使用 C 语言和 POSIX API 可以方便地在 Linux 下实现 UDP 网络通信。
后续你可以试试加入多线程、非阻塞 IO 或使用 select()
、poll()
等方式让你的服务端更强大。