一、UDP 基础概念
UDP(用户数据报协议)是无连接、不可靠、面向数据报的传输协议,有两种编程写法:
- 连接模式:客户端
connect 绑定服务器 → send 发送
- 无连接模式:客户端不
connect → sendto 直接指定目标发送服务器代码两种模式完全通用
二、UDP 编程必备头文件
#include <sys/types.h> // 系统基础数据类型
#include <sys/socket.h> // 套接字核心函数
#include <netinet/in.h> // IPv4地址结构体 sockaddr_in
#include <arpa/inet.h> // IP地址转换函数
#include <unistd.h> // 关闭套接字
#include <stdio.h> // 输入输出
#include <string.h> // 内存操作
三、核心函数详解(参数表格,新手必背)
1. socket () —— 创建套接字
int socket(int domain, int type, int protocol);
| 参数名 |
含义 |
UDP 固定取值 |
作用 |
| domain |
协议族 |
AF_INET |
使用 IPv4 网络协议 |
| type |
套接字类型 |
SOCK_DGRAM |
指定为 UDP 数据报套接字 |
| protocol |
具体协议 |
0 |
系统自动匹配 UDP 协议 |
| 返回值 |
执行结果 |
成功:文件描述符失败:-1 |
用于后续通信操作 |
2. bind () —— 服务器绑定端口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
表格
| 参数名 |
含义 |
取值 |
作用 |
| sockfd |
套接字 |
socket () 返回值 |
服务器监听套接字 |
| addr |
地址结构体 |
服务器 IP + 端口 |
绑定本机地址与端口 |
| addrlen |
结构体长度 |
sizeof(struct sockaddr_in) |
地址长度 |
| 返回值 |
执行结果 |
成功:0失败:-1 |
服务器必须绑定成功 |
3. sendto () —— UDP 无连接发送
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
| 参数名 |
含义 |
取值 |
作用 |
| sockfd |
客户端套接字 |
socket () 返回值 |
通信通道 |
| buf |
发送缓冲区 |
自定义数组 |
存储要发送的数据 |
| len |
数据长度 |
strlen(缓冲区) |
实际发送字节数 |
| flags |
标志位 |
0 |
默认无特殊操作 |
| dest_addr |
目标地址 |
服务器 IP + 端口 |
指定接收方 |
| addrlen |
地址长度 |
结构体大小 |
地址参数长度 |
| 返回值 |
发送长度 |
成功:发送字节数失败:-1 |
- |
4. recvfrom () —— UDP 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
| 参数名 |
含义 |
取值 |
作用 |
| sockfd |
服务器套接字 |
socket () 返回值 |
监听通道 |
| buf |
接收缓冲区 |
自定义数组 |
存储接收数据 |
| len |
最大长度 |
缓冲区大小 - 1 |
防止溢出 |
| flags |
标志位 |
0 |
默认操作 |
| src_addr |
发送方地址 |
客户端 IP + 端口 |
自动存储客户端信息 |
| addrlen |
地址长度指针 |
变量地址 |
传入传出参数 |
| 返回值 |
接收长度 |
成功:接收字节数失败:-1 |
- |
5. 辅助函数
| 函数 |
功能 |
参数 |
作用 |
htons() |
端口转网络字节序 |
端口号 |
网络传输必须转换 |
inet_aton() |
字符串 IP→二进制 IP |
IP 字符串,地址指针 |
客户端配置服务器 IP |
inet_ntoa() |
二进制 IP→字符串 IP |
网络 IP |
服务器打印客户端 IP |
四、UDP 双模式通信流程图(明文版)
模式 1:UDP 连接模式(connect + send)
┌───────────────────────────┐ ┌───────────────────────────┐
│ UDP 客户端(connect) │ │ UDP 服务器 │
└────────────┬──────────────┘ └────────────┬──────────────┘
│ │
│ 1. 创建 socket() │ 1. 创建 socket()
│ │
│ 2. 配置服务器IP+端口 │ 2. 配置本地IP+端口
│ │
│ 3. connect() 绑定服务器 │ 3. bind() 绑定端口
│ │
│ 4. 键盘输入数据 │ 4. 循环阻塞 recvfrom()
│ │
│ 5. send() 发送数据 ┌──────▼───────┐
└───────────────────────────────► 接收数据+打印客户端IP │
└──────────────┘
模式 2:UDP 无连接模式(sendto)
┌───────────────────────────┐ ┌───────────────────────────┐
│ UDP 客户端(无connect) │ │ UDP 服务器 │
└────────────┬──────────────┘ └────────────┬──────────────┘
│ │
│ 1. 创建 socket() │ 1. 创建 socket()
│ │
│ 2. 配置服务器IP+端口 │ 2. bind() 绑定端口
│ │
│ 3. 不执行 connect(原生无连接) │ 3. 循环阻塞 recvfrom()
│ │
│ 4. 键盘输入数据 │ 等待客户端数据
│ │
│ 5. sendto() 指定目标发送数据 ┌──────▼───────┐
└───────────────────────────────────► 接收数据+打印客户端IP │
└──────────────┘
五、完整带注释代码
5.1 UDP 客户端(无连接模式 sendto)
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
// 服务器端口,必须与服务器一致
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketClient; // 客户端套接字
struct sockaddr_in tSocketServerAddr; // 服务器地址结构体
int iSendLen; // 发送数据长度
unsigned char ucSendBuf[1000]; // 发送缓冲区
int iAddrLen; // 地址长度
// 校验命令行参数:必须传入服务器IP
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
// 1. 创建UDP套接字
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
// 2. 配置服务器地址
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); // 端口转网络字节序
// 字符串IP转换为网络IP
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
// 3. 循环读取键盘数据并发送
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
iAddrLen = sizeof(struct sockaddr);
// 核心:sendto 直接指定服务器发送数据
iSendLen = sendto(iSocketClient,
ucSendBuf,
strlen(ucSendBuf),
0,
(const struct sockaddr *)&tSocketServerAddr,
iAddrLen);
// 发送失败处理
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
return 0;
}
5.2 UDP 服务器(通用,双模式兼容)
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
// 服务器监听端口
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketServer; // 服务器监听套接字
struct sockaddr_in tSocketServerAddr; // 服务器地址
struct sockaddr_in tSocketClientAddr; // 客户端地址
int iAddrLen; // 地址长度
int iRecvLen; // 接收数据长度
unsigned char ucRecvBuf[1000]; // 接收缓冲区
// 1. 创建UDP套接字
iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}
// 2. 配置服务器地址:监听所有网卡
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT);
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
// 3. 绑定IP+端口(服务器必须执行)
if (-1 == bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)))
{
printf("bind error!\n");
return -1;
}
// 4. 循环接收客户端数据
while (1)
{
iAddrLen = sizeof(struct sockaddr);
// 接收数据并获取客户端IP/端口
iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0,
(struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0'; // 添加结束符,防止乱码
printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
}
close(iSocketServer);
return 0;
}
六、编译与运行
1. 编译服务器
gcc udp_server.c -o udp_server
2. 运行服务器
./udp_server
3. 编译客户端
gcc udp_client.c -o udp_client
4. 运行客户端
./udp_client 127.0.0.1
七、UDP 双模式对比
| 对比项 |
connect + send(连接模式) |
sendto(无连接模式) |
| 连接状态 |
伪连接,绑定固定服务器 |
真正无连接 |
| 发送方式 |
无需指定目标 |
每次指定目标地址 |
| 灵活性 |
低,仅发一个服务器 |
高,可发多个服务器 |
| UDP 本质 |
封装简化写法 |
原生标准写法 |
| 适用场景 |
固定单点通信 |
多目标 / 广播通信 |
八、新手常见问题
- bind 报错:端口被占用,更换端口即可
- 收不到数据:IP / 端口填写错误
- 乱码:接收数据未加
\0 结束符
- 发送失败:地址结构体配置错误
九、核心总结
- 服务器固定流程:
socket → bind → recvfrom
- 无连接客户端流程:
socket → 配置地址 → sendto
- 连接客户端流程:
socket → connect → send
recvfrom 自动获取客户端信息,是 UDP 服务器核心
- 无连接模式是 UDP 最标准、最原生的写法
所有评论(0)