UDP(用户数据报协议)是无连接的传输层协议,与TCP不同,UDP不需要建立连接,也不保证数据的顺序和可靠性。因此,UDP的通信模型要简单得多,适合那些对实时性要求较高但可以容忍部分数据丢失的应用,如视频流、DNS查询等。
UDP套接字编程的主要系统调用:
socket()
bind()
sendto()
/recvfrom()
close()
这些调用与TCP类似,但不需要 listen()
和 accept()
,因为UDP是无连接的。下面是详细介绍:
1. socket()
socket()
用于创建一个UDP套接字。
int socket(int domain, int type, int protocol);
- domain:协议族,通常为
AF_INET
(IPv4)或AF_INET6
(IPv6)。 - type:套接字类型,UDP使用
SOCK_DGRAM
。 - protocol:一般设为0,表示默认协议(UDP协议)。
返回值:成功时返回套接字的文件描述符,失败时返回 -1。
示例:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
2. bind()
bind()
用于将UDP套接字绑定到一个本地地址(IP地址和端口号)。在服务器端程序中,通常需要bind()
来确保数据发送到正确的端口。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:
socket()
函数返回的套接字文件描述符。 - addr:
struct sockaddr_in
,包含要绑定的本地地址(IP和端口)。 - addrlen:
addr
结构体的大小。
返回值:成功时返回0,失败时返回-1。
示例:
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到本地IP地址
server_addr.sin_port = htons(8080); // 绑定到端口8080
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
3. sendto()
sendto()
用于发送数据到指定的目标地址(IP地址和端口)。因为UDP是无连接的,所以每次发送时都要指定目标。
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:目标地址(包含目标IP和端口的
struct sockaddr_in
)。 - addrlen:目标地址结构体的大小。
返回值:成功时返回发送的字节数,失败时返回-1。
示例:
struct sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &client_addr.sin_addr);
const char *message = "Hello, UDP!";
sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&client_addr, sizeof(client_addr));
4. recvfrom()
recvfrom()
用于从指定的源地址接收数据。此函数适用于UDP,因为UDP是无连接的,数据可能从任何源地址到达。
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:发送方的地址,存储发送方的IP地址和端口。
- addrlen:src_addr结构体的大小。
返回值:成功时返回接收的字节数,失败时返回-1。
示例:
char buffer[1024];
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &addr_len);
buffer[n] = '\0'; // 确保接收到的数据以\0结束
printf("Received from client: %s\n", buffer);
5. close()
close()
用于关闭UDP套接字,释放与之关联的资源。
int close(int sockfd);
- sockfd:套接字文件描述符。
返回值:成功时返回0,失败时返回-1。
示例:
close(sockfd);
完整的UDP服务器和客户端示例
UDP服务器端示例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
int main() {
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr, client_addr;
// 创建UDP套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation 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");
exit(EXIT_FAILURE);
}
int len, n;
len = sizeof(client_addr);
// 接收客户端发送的数据
n = recvfrom(sockfd, (char *)buffer, 1024, 0, (struct sockaddr *)&client_addr, &len);
buffer[n] = '\0';
printf("Client : %s\n", buffer);
// 向客户端发送回复
const char *reply = "Hello from server";
sendto(sockfd, reply, strlen(reply), 0, (const struct sockaddr *)&client_addr, len);
printf("Hello message sent.\n");
// 关闭套接字
close(sockfd);
return 0;
}
UDP客户端示例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
int main() {
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
// 创建UDP套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
// 向服务器发送消息
const char *message = "Hello from client";
sendto(sockfd, message, strlen(message), 0, (const struct sockaddr *)&server_addr, sizeof(server_addr));
printf("Hello message sent.\n");
// 接收服务器的回复
int n, len = sizeof(server_addr);
n = recvfrom(sockfd, (char *)buffer, 1024, 0, (struct sockaddr *)&server_addr, &len);
buffer[n] = '\0';
printf("Server : %s\n", buffer);
// 关闭套接字
close(sockfd);
return 0;
}
总结:UDP套接字编程的主要步骤
- socket():创建UDP套接字。
- bind():在服务器端绑定本地地址和端口。
- sendto():发送数据到目标地址。
- recvfrom():从指定的源地址接收数据。
- close():关闭套接字。
UDP编程的逻辑相对简单,但没有连接管理和传输保证。因此,程序设计时需要考虑数据丢失和无序到达的问题。