前言
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的、不可靠的、面向数据报的传输层协议。它广泛应用于需要高实时性且对数据传输可靠性要求不高的场景,如实时音视频传输、在线游戏等。本文将详细介绍UDP通讯协议的原理、各关键函数的作用及其实现实例,并最后进行总结。
UDP协议原理
无连接性
UDP是无连接的协议,通信双方在传输数据之前不需要建立连接,也不需要维护连接状态。这种特性使得UDP在处理实时数据和快速数据传输时非常高效。
不可靠性
UDP不提供数据包的确认和重传机制,也不保证数据包的顺序性,因此数据传输过程中可能会出现丢包、重复或乱序等情况。UDP的设计目标之一就是降低通信的开销,以支持高吞吐量和低延迟的通信需求。
面向数据报
UDP以数据报为基本单位进行通信,每个数据报是一个独立的、完整的消息,具有独立的头部和数据部分。UDP数据报格式包括源端口号、目的端口号、长度和校验和等字段。
-源端口号(16 bits):标识发送端的端口号。
-目的端口号(16 bits):标识接收端的端口号。
-长度(16 bits):表示UDP数据报的总长度,包括UDP头部和数据部分。
-校验和(16 bits):用于检测数据报的完整性,以确保数据在传输过程中没有被损坏。
-数据(可变长度):包含了应用程序要传输的实际数据。
相应函数
socket()
socket()函数用于创建一个套接字,它是网络通信的端点。对于UDP通信,socket()函数的第三个参数应设置为SOCK_DGRAM,表示创建的是数据报套接字。
int socket(int domain, int type, int protocol);
作用:创建一个新的套接字。
参数说明:
-domain:指定协议族,对于IPv4使用AF_INET。
-type:指定套接字类型,对于UDP使用SOCK_DGRAM。
-protocol:通常设置为0,自动选择协议。
bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:将套接字绑定到一个特定的地址和端口上。
参数说明:
-sockfd:由socket()返回的套接字描述符。
-addr:指向sockaddr结构的指针,包含地址和端口信息。
-addrlen:地址结构体的长度。
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:指向目标地址的sockaddr结构指针。
-addrlen:目标地址结构体的长度。
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:指向sockaddr结构的指针,用于存储发送方的地址信息。
-addrlen:地址结构体的长度指针。
示例
本实例在windows 11环境下的vs2019中运行,ubuntu环境中的头文件与此不同。但各函数功能基本一样。在同一台电脑中进行发送和接受,进行两个实例。
字符串整体发送示例
服务端
#include <iostream>
#include <cstring>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "12345" // 你可以根据需要更改这个端口号,但要确保它没有被其他应用占用
int main() {
WSADATA wsaData;
SOCKET sock = INVALID_SOCKET;
struct addrinfo hints, * res = NULL, * p = NULL;
int iResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
struct sockaddr_storage their_addr;
socklen_t addr_size = sizeof(their_addr);
// 初始化Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
std::cerr << "WSAStartup failed with error: " << iResult << std::endl;
return 1;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; // 使用IPv4
hints.ai_socktype = SOCK_DGRAM; // UDP
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE; // 允许绑定到任意地址
// 解析监听地址和端口
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &res);
if (iResult != 0) {
std::cerr << "getaddrinfo failed with error: " << iResult << std::endl;
WSACleanup();
return 1;
}
// 创建套接字
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock == INVALID_SOCKET) {
std::cerr << "socket failed with error: " << WSAGetLastError() << std::endl;
freeaddrinfo(res);
WSACleanup();
return 1;
}
// 绑定套接字到地址和端口
iResult = bind(sock, res->ai_addr, (int)res->ai_addrlen);
if (iResult == SOCKET_ERROR) {
std::cerr << "bind failed with error: " << WSAGetLastError() << std::endl;
freeaddrinfo(res);
closesocket(sock);
WSACleanup();
return 1;
}
freeaddrinfo(res); // 不再需要地址信息
std::cout << "UDP Server listening on port " << DEFAULT_PORT << std::endl;
// 接收数据
while (true) {
iResult = recvfrom(sock, recvbuf, recvbuflen, 0,
(struct sockaddr*)&their_addr, &addr_size);
if (iResult == SOCKET_ERROR) {
std::cerr << "recvfrom failed with error: " << WSAGetLastError() << std::endl;
closesocket(soc