上一个章节学完如何实现一个带有数据边界的TCP,这个章节就要学习如何写一个UDP。
UDP
本次实验的效果
UDP网络编程的最大特点
一台电脑只需要一个UDP socket 就可以实现和其他的任何电脑交互。理论上UDP是不存在server和client的区别,你只要对外发送消息你就是server, 你只要读取消息你就是client.
有一个for循环,他会接收UDP 包, UDP 在接收信息时是以数据报(包)为单位进行接收的。
在 sleep
期间的情况:
- 由于
recvfrom()
没有被调用,操作系统会将接收到的数据存储在 socket 的接收缓冲区 中。 - 如果缓冲区足够大,3 条数据都会被存储在缓冲区中,等待
recvfrom()
逐条读取。 - 如果缓冲区已满,则超出缓冲区大小的任何新数据会被丢弃(数据报丢失)。
数据包的长度限制
-
UDP 数据包的最大长度:
-
UDP 协议中,数据包的长度限制是 64 KB(65535 字节),这是由 UDP 报文头中的 长度字段(Length) 决定的。
-
UDP 报文结构:
-
UDP 头部长度固定为 8 字节。
-
UDP 数据部分(有效载荷,Payload)的最大长度为 65507 字节:
65535(UDP 数据包总长度) - 8(UDP 头部) - 20(IP 头部) = 65507 字节
-
因此,UDP 数据包的有效数据部分最大只能有 65507 字节。
-
-
-
实际传输中的限制:
-
在真实网络中,UDP 数据包的长度还会受到底层协议(如以太网)的 MTU(最大传输单元) 限制。
- 以太网的默认 MTU 为 1500 字节,减去 IP 头部(20 字节)和 UDP 头部(8 字节),单个 UDP 数据包的有效数据部分最大为 1472 字节。
- 如果数据包超过 MTU,大多数网络设备会对数据进行 IP 分片(将数据包拆分成多个片段进行传输)。
-
-
分片的影响:
- UDP 本身不处理重组或分片问题。如果数据包被分片,分片的重组由 IP 层完成。
- 如果网络丢失了某个片段,整个 UDP 数据包都会被丢弃(因为 UDP 没有重传机制)。
程序中的实际数据长度
在您的程序中,接收数据的缓冲区大小由 BUF_SIZE
定义:
#define BUF_SIZE 30
这表示程序一次最多可以接收 30 字节的 UDP 数据(包括 '\0' 终止符) 。
- 如果接收的数据包大于缓冲区大小(
BUF_SIZE
),超出部分会被 截断。 recvfrom()
的返回值(str_len
)会告知实际接收到的数据长度。
server代码
下面是server.c代码
#include <stdio.h> // For standard input and output
#include <stdlib.h> // For exit() and atoi()
#include <string.h> // For memset()
#include <arpa/inet.h> // For sockaddr_in, htons(), htonl(), etc.
#include <unistd.h> // For close(), sleep()
#define BUF_SIZE 30 // Define buffer size for messages
// Function declaration for error handling
void error_handling(char *message);
int main(int argc, char *argv[]) {
int sock; // Socket file descriptor
char message[BUF_SIZE]; // Buffer to store incoming messages
struct sockaddr_in my_adr, your_adr; // Structures for socket address information
socklen_t adr_sz; // Variable to hold the size of the address structure
int str_len, i; // Variables for string length and loop counter
// Check if the program is run with the correct number of arguments
if (argc != 2) {
printf("Usage: %s <port>\n", argv[0]); // Display usage instructions
exit(1); // Exit the program if arguments are invalid
}
// Create a UDP socket
sock = socket(PF_INET, SOCK_DGRAM, 0); // PF_INET for IPv4, SOCK_DGRAM for UDP
if (sock == -1) // Check if socket creation failed
error_handling("socket() error");
// Initialize the my_adr structure with zeros
memset(&my_adr, 0, sizeof(my_adr));
my_adr.sin_family = AF_INET; // Set address family to IPv4
my_adr.sin_addr.s_addr = htonl(INADDR_ANY); // Accept connections from any IP address
my_adr.sin_port = htons(atoi(argv[1])); // Convert port number from string to network byte order
// Bind the socket to the specified IP address and port
if (bind(sock, (struct sockaddr *)&my_adr, sizeof(my_adr)) == -1) {
error_handling("bind() error"); // Handle bind error
}
// Loop to receive messages
for (i = 0; i < 3; i++) {
sleep(5); // Delay 5 seconds between receiving messages
// Initialize the size of the sender's address structure
adr_sz = sizeof(your_adr);
// Receive a message from a client
str_len = recvfrom(sock, message, BUF_SIZE, 0,
(struct sockaddr *)&your_adr, &adr_sz);
// Print the received message
printf("Message %d: %s \n", i + 1, message);
}
// Close the socket after finishing
close(sock);
return 0; // Indicate successful program termination
}
// Error handling function
void error_handling(char *message) {
// Print the error message and exit the program
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
client代码
发送3个数据报给server
下面是client.c 代码
#include <stdio.h> // For standard input/output
#include <stdlib.h> // For exit() and atoi()
#include <string.h> // For memset()
#include <arpa/inet.h> // For networking functions (inet_addr, htons, etc.)
#include <unistd.h> // For close()
#define BUF_SIZE 30 // Define the buffer size for messages
// Error handling function declaration
void error_handling(char *message);
int main(int argc, char *argv[]) {
int sock; // Socket file descriptor
// Messages to send
char msg1[] = "Hi!";
char msg2[] = "I'm another UDP host!";
char msg3[] = "Nice to meet you";
struct sockaddr_in your_adr; // Structure for the recipient's address
socklen_t your_adr_sz; // Size of the recipient's address structure
// Check for correct number of arguments
if (argc != 3) {
printf("Usage: %s <IP> <port>\n", argv[0]); // Display usage instructions
exit(1); // Exit if arguments are incorrect
}
// Create a UDP socket
sock = socket(PF_INET, SOCK_DGRAM, 0); // PF_INET for IPv4, SOCK_DGRAM for UDP
if (sock == -1) // Check if socket creation failed
error_handling("socket() error");
// Initialize the recipient's address structure
memset(&your_adr, 0, sizeof(your_adr)); // Zero out the structure
your_adr.sin_family = AF_INET; // Use IPv4
your_adr.sin_addr.s_addr = inet_addr(argv[1]); // Set the recipient's IP address
your_adr.sin_port = htons(atoi(argv[2])); // Set the recipient's port
// Send messages to the specified recipient
sendto(sock, msg1, sizeof(msg1), 0, (struct sockaddr *)&your_adr, sizeof(your_adr));
sendto(sock, msg2, sizeof(msg2), 0, (struct sockaddr *)&your_adr, sizeof(your_adr));
sendto(sock, msg3, sizeof(msg3), 0, (struct sockaddr *)&your_adr, sizeof(your_adr));
// Close the socket after sending
close(sock);
return 0; // Exit successfully
}
// Error handling function
void error_handling(char *message) {
// Print the error message and exit
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
结果如图
server 先启动监听socket
client 发送3条消息就关闭
需要注意的点
UDP在传输信息的时候也要转换成网络字节序传输,到本地再转换成本地字节序;但是这次实验消息是string类型,不需要考虑字节序问题。因为,字符串的每个字符本质上是一个字节(8 位),不涉及主机字节序和网络字节序的差异。