1、前言
1.1 定义
UDP 是一种无连接的传输协议,它不需要在通信之前建立连接,也不保证数据传输的可靠性。UDP 将数据打包成数据报进行传输,不进行数据分段和重组,因此传输效率较高。
1.2 应用场景
- 实时性要求高的应用:由于 UDP 不保证数据传输的可靠性,适用于实时性要求高的应用,如音频/视频流的传输、在线游戏等。
- 广播和多播通信:UDP 支持广播和多播通信,适用于需要向多个主机发送相同数据的场景。
- 简单的数据传输:对于一些简单的数据传输场景,如 DNS 查询、SNMP 等,UDP 是一个较为合适的选择。
1.3 网络中的作用
在网络分层模型中,UDP 位于传输层,与 TCP 一起为应用层提供数据传输服务。
- 为应用层提供无连接的数据传输服务:UDP 不需要建立连接,适用于一些实时性要求高、数据传输较简单的应用场景。
- 提供数据报服务:UDP 将数据打包成数据报进行传输,不进行数据分段和重组,传输效率较高。
- 支持广播和多播通信:UDP 支持向多个主机发送相同数据,适用于广播和多播通信场景。
2、常用接口
2.1 socket函数
创建一个套接字,用于后续的数据传输。
int socket(int domain, int type, int protocol);
入参:
domain
:指定协议族,通常为AF_INET
(IPv4)或AF_INET6
(IPv6)。type
:指定套接字类型,通常为SOCK_DGRAM
表示 UDP 套接字。protocol
:指定协议,通常为0
表示使用默认协议。
返回值:
- 成功时返回套接字描述符,失败时返回
-1
。
2.2 blind函数
将套接字绑定到特定的IP地址和端口号。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
入参:
sockfd
:套接字描述符,由socket()
函数返回addr
:指向sockaddr
结构体的指针,包含要绑定的IP地址和端口号信息addrlen
:sockaddr
结构体的长度
返回值:
- 成功时返回0,失败时返回-1
2.3 sendto函数
向指定地址发送数据。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
入参:
sockfd
:套接字描述符。buf
:指向要发送数据的缓冲区。len
:要发送数据的长度。flags
:发送标志,通常为0
。dest_addr
:目标地址的结构体指针,可以是struct sockaddr_in
或struct sockaddr_in6
。addrlen
:目标地址结构体的长度。
返回值:
- 成功时返回发送的字节数,失败时返回
-1
。
2.4 recvfrom 函数
从指定的目标地址接收数据。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
入参:
sockfd
:套接字描述符buf
:指向接收数据的缓冲区len
:缓冲区长度flags
:接收控制标志,一般设为0src_addr
:指向发送方地址信息的sockaddr
结构体指针addrlen
:发送方地址结构体的长度
返回值:
- 成功时返回接收的字节数,失败时返回-1
3、编程测试
3.1 一般编程步骤
UDP 服务器端编程流程:
-
创建套接字:调用
socket()
函数创建一个UDP套接字。 -
绑定地址:调用
bind()
函数将服务器端地址绑定到套接字上。 -
接收数据:循环调用
recvfrom()
函数接收客户端发送的数据。 -
处理数据:对接收到的数据进行处理,可以根据需求进行相应的逻辑操作。
-
发送响应:根据处理结果,调用
sendto()
函数向客户端发送响应数据。 -
关闭套接字:通信结束后,调用
close()
函数关闭套接字。
UDP 客户端编程流程:
-
创建套接字:调用
socket()
函数创建一个UDP套接字。 -
准备数据:准备要发送的数据,并将目标服务器地址信息填入
sockaddr
结构体中。 -
发送数据:调用
sendto()
函数向服务器发送数据。 -
接收响应:调用
recvfrom()
函数接收服务器端的响应数据。 -
处理响应:对接收到的响应数据进行处理,可以根据需求进行相应的逻辑操作。
-
关闭套接字:通信结束后,调用
close()
函数关闭套接字。
3.2 服务器编程
编写服务器代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUF_SIZE 1024
int main()
{
int sockfd;
struct sockaddr_in server_addr, client_addr;
char buffer[BUF_SIZE];
// 创建 UDP 套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 绑定服务器地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
printf("UDP Server listening on port %d...\n", PORT);
socklen_t len = sizeof(client_addr);
// 接收数据
recvfrom(sockfd, buffer, BUF_SIZE, 0, (struct sockaddr*)&client_addr, &len);
printf("Received from client: %s\n", buffer);
// 回复客户端数据
sendto(sockfd, "Message received", strlen("Message received"), 0, (struct sockaddr*)&client_addr, len);
close(sockfd);
return 0;
}
3.3 客户端编程
编写客户端代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8888
#define SERVER_IP "127.0.0.1"
#define BUF_SIZE 1024
int main()
{
int sockfd;
struct sockaddr_in server_addr;
char buffer[BUF_SIZE] = "msg from client";
// 创建 UDP 套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 设置服务器地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
server_addr.sin_port = htons(PORT);
// 发送数据到服务器
sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 接收服务器回复的数据
recvfrom(sockfd, buffer, BUF_SIZE, 0, NULL, NULL);
printf("Server replied: %s\n", buffer);
close(sockfd);
return 0;
}
3.4 测试
开启两个终端进行测试,先开启服务器,再开启客户端,测试结果如下:
4、总结
本文讲解了UDP的定义以及应用场景,列出了编程中常用的接口,最后编写程序进行测试。