传输层,从端口号到 UDP 协议
目录
一、传输层:网络通信的 “快递分拣站”
二、端口号:传输层的 “门牌号”
2.1 什么是端口号?
2.2 五元组:唯一标识一次通信
2.3 端口号的范围划分
(1)知名端口号(0-1023):“公共服务的固定门牌号”
(2)动态端口号(1024-65535):“客户端的临时门牌号”
2.4 两个常见问题:端口号的 “绑定规则”
问题 1:一个进程能绑定多个端口号吗?—— 可以!
问题 2:一个端口号能被多个进程绑定吗?—— 通常不可以!
三、UDP 协议:传输层的 “快速快递”
3.1 UDP 的报头:数据的 “快递面单”
3.2 UDP 的三大核心特点
(1)无连接:“不用提前打招呼,直接发”
(2)不可靠:“发完不管,丢了不补”
(3)面向数据报:“整包发,整包收,不能拆”
3.3 UDP 的缓冲区:“临时存放数据的小仓库”
(1)发送缓冲区:“没有真正的缓存,直接交内核”
(2)接收缓冲区:“临时存数据,供应用层读取”
3.4 UDP 的 “全双工” 特性:“同时收发,互不干扰”
3.5 UDP 的使用注意事项
(1)数据长度限制:单次最大传 64K
(2)基于 UDP 的常见应用层协议
四、实战:手把手写 UDP 客户端和服务器
4.1 核心函数详解
(1)创建 UDP 套接字:socket()
(2)绑定端口:bind()
(3)发送数据:sendto()
(4)接收数据:recvfrom()
(5)关闭套接字:close()
4.2 完整代码:UDP 服务器
4.3 完整代码:UDP 客户端
4.4 代码运行步骤
(1)编译代码
(2)启动服务器
(3)启动客户端(新终端)
(4)查看通信效果
五、总结
在网络通信中,有一层 “承上启下” 的关键环节 ——传输层。它一边接收应用层(比如 HTTP、QQ)的 “数据包裹”,一边通过网络层(IP 协议)把包裹送到目标主机,还得确保 “包裹” 能精准交到对方的应用程序手上。这篇博客会从传输层的 “门牌号”(端口号)讲起,深入 UDP 协议的原理,最后用实战代码带你亲手实现 UDP 通信,让你彻底搞懂传输层是怎么工作的。
一、传输层:网络通信的 “快递分拣站”
先搞明白传输层的核心作用:
- 承上:从应用层接收数据,给数据 “贴标签”(比如端口号),告诉网络层 “这个数据要交给对方的哪个应用”;
- 启下:接收网络层传来的外部数据,根据 “标签” 把数据交给本机对应的应用层程序;
- 核心目标:解决 “数据交给谁” 的问题(定位应用程序),并提供不同的传输策略(比如 UDP 的 “快”、TCP 的 “稳”)。
举个生活类比:你在淘宝买了东西,商家(应用层)把快递(数据)交给快递站(传输层)。快递站会贴上面单 —— 收件人地址(IP)、收件人门牌号(端口号)、快递类型(协议,比如 UDP/TCP),然后交给物流(网络层)运输。对方快递站收到后,根据门牌号把快递交给收件人(对方应用程序)。
二、端口号:传输层的 “门牌号”
要让数据精准找到对方的应用程序,就需要一个 “唯一标识”——端口号(Port)。它就像你家的门牌号,快递员(传输层)根据门牌号,才能把快递(数据)准确送到你家(应用程序),而不是邻居家。
2.1 什么是端口号?
端口号是一个16 位的整数(范围 0-65535),用于标识一台主机上的 “唯一应用程序”。每个需要网络通信的应用(比如浏览器、微信、服务器),都会占用一个或多个端口号。
比如:
- 浏览器访问百度时,百度服务器的 HTTP 服务用80 端口;
- 你用微信发消息时,微信客户端会被系统分配一个动态端口号(比如 56789)。
2.2 五元组:唯一标识一次通信
传输层要区分 “哪个应用的哪次通信”,靠的是五元组—— 用 5 个信息组合,唯一确定一次网络通信:
| 组成部分 | 作用类比 | 举例 |
|---|---|---|
| 源 IP 地址 | 寄件人地址 | 你的电脑 IP:192.168.1.100 |
| 源端口号 | 寄件人门牌号 | 微信客户端端口:56789 |
| 目的 IP 地址 | 收件人地址 | 微信服务器 IP:123.123.123.123 |
| 目的端口号 | 收件人门牌号 | 微信服务器端口:8080 |
| 协议号 | 快递类型(如顺丰、中通) | UDP 协议号:17;TCP 协议号:6 |
为什么需要五元组?比如你同时用浏览器(端口 5000)和微信(端口 56789)访问网络:
- 浏览器→百度(80 端口)的通信:五元组是(192.168.1.100:5000 → 202.108.22.5:80,TCP);
- 微信→微信服务器(8080 端口)的通信:五元组是(192.168.1.100:56789 → 123.123.123.123:8080,UDP);传输层通过五元组,能准确区分这两次通信,不会把百度的响应发给微信,也不会把微信的消息发给浏览器。
查看五元组的实战命令:在 Linux/macOS 终端输入netstat -n,就能看到当前所有通信的五元组信息:
plaintext
Proto Recv-Q Send-Q Local Address Foreign Address State udp 0 0 192.168.1.100:56789 123.123.123.123:8080 ESTABLISHED tcp 0 0 192.168.1.100:5000 202.108.22.5:80 ESTABLISHEDLocal Address:源 IP: 源端口;Foreign Address:目的 IP: 目的端口;Proto:协议号(udp/tcp)。
2.3 端口号的范围划分
端口号总共 65536 个(0-65535),按用途分为两类:
(1)知名端口号(0-1023):“公共服务的固定门牌号”
这些端口号是 “约定俗成” 的,对应常用的公共服务,比如:
| 端口号 | 对应服务 | 应用场景 |
|---|---|---|
| 22 | SSH(远程登录) | 远程管理 Linux 服务器 |
| 21 | FTP(文件传输) | 服务器文件上传下载 |
| 23 | Telnet(远程终端) | 早期远程登录(已被 SSH 替代) |
| 80 | HTTP(网页服务) | 浏览器访问网页 |
| 443 | HTTPS(安全网页) | 淘宝、支付宝等安全访问 |
这些端口号一般由系统或服务器程序占用,普通客户端程序不能用。
(2)动态端口号(1024-65535):“客户端的临时门牌号”
普通客户端程序(比如你的浏览器、微信、游戏)启动时,会由操作系统随机分配一个这个范围的端口号,通信结束后释放。比如你打开浏览器访问百度,操作系统可能给浏览器分配 5000 端口,关闭浏览器后,5000 端口就会被释放,下次打开浏览器可能分配 5001 端口。
2.4 两个常见问题:端口号的 “绑定规则”
问题 1:一个进程能绑定多个端口号吗?—— 可以!
比如一个服务器程序(如 Nginx),可以同时绑定 80 端口(HTTP)和 443 端口(HTTPS),这样客户端既能用 HTTP 访问,也能用 HTTPS 访问。原理:进程可以创建多个 “套接字(Socket)”,每个套接字绑定一个不同的端口号,独立接收数据。
问题 2:一个端口号能被多个进程绑定吗?—— 通常不可以!
端口号的核心作用是 “唯一标识一个应用程序”,如果一个端口号被多个进程绑定,传输层收到数据后,就不知道该交给哪个进程了。例外:特殊场景下(如负载均衡),可以通过内核参数配置 “端口复用”,但这是高级操作,普通开发不用考虑。
三、UDP 协议:传输层的 “快速快递”
传输层有两个核心协议:UDP 和 TCP。今天先讲UDP(用户数据报协议)—— 它的特点是 “快、简单”,适合对实时性要求高、允许少量数据丢失的场景(比如视频通话、DNS 查询)。
3.1 UDP 的报头:数据的 “快递面单”
UDP 数据在传输时,会在应用层数据前加一个8 字节的报头(相当于快递面单),报头包含 4 个字段,每个字段都是 16 位(2 字节):
| 字段 | 长度 | 作用说明 |
|---|---|---|
| 源端口号 | 16 位 | 数据从哪个端口发来(可选,0 表示无) |
| 目的端口号 | 16 位 | 数据要送到哪个端口(必须有) |
| UDP 长度 | 16 位 | 整个 UDP 数据报的长度(报头 + 数据) |
| UDP 校验和 | 16 位 | 检查数据是否损坏(出错则丢弃) |
报头的作用:
- 传输层收到 UDP 数据后,先读 8 字节报头,根据 “目的端口号” 找到对应的应用程序;
- 通过 “UDP 长度” 知道后续要读多少字节的应用层数据;
- 通过 “校验和” 判断数据在传输中是否被篡改(比如网络干扰),如果出错,直接丢弃数据,不通知应用层。
3.2 UDP 的三大核心特点
(1)无连接:“不用提前打招呼,直接发”
UDP 通信前,不需要像 TCP 那样 “三次握手” 建立连接 —— 只要知道对方的 IP 和端口号,就能直接发送数据,就像发短信:不用先拨号,编辑好内容直接发给对方号码。
优点:通信延迟低,适合实时场景(比如语音聊天,不能等连接建立再说话);缺点:不知道对方是否在线,可能发了 “空包”(对方没收到)。
(2)不可靠:“发完不管,丢了不补”
UDP 没有 “确认机制” 和 “重传机制”:
- 发送方发完数据,不知道对方有没有收到;
- 如果数据在传输中丢失(比如网络拥堵),UDP 不会重传,也不会告诉应用层 “数据丢了”。
举例:你用 UDP 发 10 条消息,对方可能只收到 8 条,丢了 2 条,但你完全不知道。适合场景:允许少量丢失的场景(比如视频通话,丢几帧画面不影响整体观看;DNS 查询,丢了再查一次就行)。
(3)面向数据报:“整包发,整包收,不能拆”
UDP 把应用层的数据当作 “一个完整的数据包”(数据报),既不拆分,也不合并:
- 发送方:调用一次
sendto(发送函数),发送 100 字节数据,UDP 就把这 100 字节当作一个数据报发送; - 接收方:必须调用一次
recvfrom(接收函数),一次性接收这 100 字节,不能分 10 次每次收 10 字节。
反例:如果发送方分两次各发 50 字节,接收方不能一次收 100 字节 —— 会只收到第一个 50 字节,第二个 50 字节下次收。
3.3 UDP 的缓冲区:“临时存放数据的小仓库”
UDP 的发送和接收缓冲区是分开的,作用不同,很多人容易搞混,这里重点讲:
(1)发送缓冲区:“没有真正的缓存,直接交内核”
UDP 没有 “真正的发送缓冲区”—— 应用层调用sendto发送数据时,数据会直接拷贝到内核缓冲区,内核尽快把数据发给网络层,发送完成后,内核缓冲区的 data 就会被释放,不会保留(不像 TCP 会保留数据用于重传)。
注意:如果内核缓冲区满了,sendto会阻塞(默认)或返回错误,需要等内核腾出空间。
(2)接收缓冲区:“临时存数据,供应用层读取”
UDP 有 “接收缓冲区”—— 内核收到 UDP 数据后,会把数据报放到接收缓冲区,应用层调用recvfrom时,从缓冲区里取一个数据报。
接收缓冲区的两个关键特性:
- 无序存放:数据报的接收顺序可能和发送顺序不一致(比如网络路由不同),缓冲区会按 “收到的顺序” 存放,不管发送顺序;
- 满了就丢:如果缓冲区满了,新收到的 UDP 数据报会直接被丢弃,不会通知应用层。
为什么需要接收缓冲区?如果没有接收缓冲区,应用层必须 “实时读取” 数据 —— 一旦应用层忙(比如处理其他任务),内核收到的数据就会被丢弃,浪费网络资源。有了缓冲区,数据可以临时存放,应用层空闲时再读。
3.4 UDP 的 “全双工” 特性:“同时收发,互不干扰”
UDP 的套接字(Socket)既能发送数据,也能接收数据,这种特性叫 “全双工”,就像打电话:你可以一边说话(发送),一边听对方说话(接收),不用等对方说完再开口。
举例:UDP 客户端和服务器可以同时给对方发消息,不用等对方回复再发。
3.5 UDP 的使用注意事项
(1)数据长度限制:单次最大传 64K
UDP 报头的 “UDP 长度” 是 16 位,最大能表示 65535 字节(64K),所以单次 UDP 数据报的最大长度是 64K(包含 8 字节报头),实际应用层数据最大约 64K-8=65527 字节。
如果要传超过 64K 的数据(比如传一个 100K 的文件),需要在应用层手动分包:
- 发送方:把 100K 文件分成 2 个包(64K 和 36K),每次发一个,给每个包加 “包序号”(比如 1、2);
- 接收方:收到包后,按 “包序号” 拼接成完整文件。
(2)基于 UDP 的常见应用层协议
UDP 虽然不可靠,但因为 “快”,被很多核心协议采用:
| 协议名称 | 用途 | 为什么用 UDP? |
|---|---|---|
| DNS | 域名解析(如百度→IP) | 查询快,丢了再查一次,延迟低 |
| DHCP | 自动分配 IP 地址 | 不需要可靠,一次广播就能分配 IP |
| TFTP | 简单文件传输 | 协议简单,适合小文件(如路由器固件) |
| 视频 / 语音 | 实时通话 | 实时性优先,丢几帧不影响体验 |
四、实战:手把手写 UDP 客户端和服务器
我们用 C++ 写一个简单的 UDP 通信程序:
- 服务器:绑定 8888 端口,接收客户端消息,返回 “收到了:XXX”;
- 客户端:输入消息,发给服务器,接收服务器的回复并显示。
4.1 核心函数详解
在写代码前,先搞懂 UDP 编程的 4 个核心函数:socket、bind、sendto、recvfrom、close。
(1)创建 UDP 套接字:
socket()#include <sys/socket.h> int socket(int domain, int type, int protocol);
- 作用:创建一个 UDP “通信管道”(套接字),用于收发数据。
- 参数:
domain:协议族,必须是AF_INET(IPv4);type:套接字类型,SOCK_DGRAM(表示 UDP);protocol:协议,填 0(自动选择 UDP 协议);- 返回值:
- 成功:返回 “套接字描述符”(非负整数,类似文件句柄);
- 失败:返回 - 1,错误原因存在
errno中。- 示例:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket创建失败"); // 打印错误原因 exit(1); }(2)绑定端口:
bind()#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 作用:给 UDP 套接字绑定一个固定的 IP 和端口号(服务器必须绑定,客户端可选)。
- 参数:
sockfd:socket()返回的套接字描述符;addr:存放 IP 和端口的结构体(struct sockaddr_in);addrlen:结构体的大小(sizeof(struct sockaddr_in));- 返回值:
- 成功:0;
- 失败:-1。
- 示例(服务器绑定 8888 端口):
struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); // 初始化结构体 server_addr.sin_family = AF_INET; // IPv4 server_addr.sin_port = htons(8888); // 端口号(转网络字节序) server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡的IP(0.0.0.0) if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("绑定端口失败"); close(sockfd); exit(1); }- 为什么服务器必须绑定端口?客户端需要知道服务器的端口号才能发消息,如果服务器不绑定固定端口,每次启动端口号都变,客户端找不到服务器。
(3)发送数据:
sendto()#include <sys/socket.h> ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
- 作用:通过 UDP 套接字给指定 IP 和端口的目标发送数据。
- 参数:
sockfd:套接字描述符;buf:要发送的数据缓冲区(比如字符串);len:数据的长度(字节数);flags:发送标志,填 0(默认);dest_addr:目标的 IP 和端口结构体(客户端填服务器的,服务器填客户端的);addrlen:目标结构体的大小;- 返回值:
- 成功:返回实际发送的字节数;
- 失败:返回 - 1。
- 示例(客户端发消息给服务器):
const char *msg = "Hello UDP Server!"; struct sockaddr_in server_addr; // 服务器的IP和端口 // 初始化server_addr(略,见客户端代码) ssize_t n = sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (n == -1) { perror("发送失败"); close(sockfd); exit(1); } printf("发送了%d字节:%s\n", (int)n, msg);
(4)接收数据:
recvfrom()#include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
- 作用:从 UDP 套接字接收数据,并获取发送方的 IP 和端口。
- 参数:
sockfd:套接字描述符;buf:存放接收数据的缓冲区;len:缓冲区的大小(避免溢出);flags:接收标志,填 0(默认,阻塞等待数据);src_addr:输出参数,存放发送方的 IP 和端口;addrlen:输入输出参数,先填sizeof(src_addr),接收后存实际长度;- 返回值:
- 成功:返回实际接收的字节数;
- 失败:返回 - 1;
- 注意:UDP 没有 “连接关闭” 的概念,不会返回 0。
- 示例(服务器接收客户端消息):
char buf[1024] = {0}; // 缓冲区,大小1024字节 struct sockaddr_in client_addr; // 存放客户端的IP和端口 socklen_t client_len = sizeof(client_addr); ssize_t n = recvfrom(sockfd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&client_addr, &client_len); if (n == -1) { perror("接收失败"); close(sockfd); exit(1); } buf[n] = '\0'; // 加字符串结束符 // 把客户端IP从网络字节序转成字符串 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); printf("收到来自%s:%d的消息(%d字节):%s\n", client_ip, ntohs(client_addr.sin_port), (int)n, buf);
(5)关闭套接字:
close()#include <unistd.h> int close(int fd);
- 作用:关闭 UDP 套接字,释放资源。
- 参数:
fd:套接字描述符;- 返回值:成功 0,失败 - 1;
- 示例:
close(sockfd);
4.2 完整代码:UDP 服务器
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8888 // 服务器绑定的端口号 #define BUF_SIZE 1024 // 缓冲区大小 int main() { // 1. 创建UDP套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket创建失败"); exit(1); } printf("UDP套接字创建成功(fd=%d)\n", sockfd); // 2. 绑定端口(服务器必须绑定) struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; // IPv4 server_addr.sin_port = htons(PORT); // 端口号转网络字节序 server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡IP(0.0.0.0) if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("绑定端口失败"); close(sockfd); exit(1); } printf("服务器绑定端口%d成功,等待客户端消息...\n", PORT); // 3. 循环接收客户端消息,并发回响应 while (1) { char buf[BUF_SIZE] = {0}; struct sockaddr_in client_addr; // 存放客户端信息 socklen_t client_len = sizeof(client_addr); // 接收客户端消息 ssize_t recv_len = recvfrom(sockfd, buf, BUF_SIZE-1, 0, (struct sockaddr*)&client_addr, &client_len); if (recv_len == -1) { perror("接收消息失败"); continue; } buf[recv_len] = '\0'; // 加字符串结束符 // 解析客户端IP和端口 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); uint16_t client_port = ntohs(client_addr.sin_port); printf("收到来自%s:%d的消息:%s\n", client_ip, client_port, buf); // 构造响应消息(在客户端消息前加“收到了:”) char resp[BUF_SIZE]; snprintf(resp, BUF_SIZE, "收到了:%s", buf); // 发送响应给客户端 ssize_t send_len = sendto(sockfd, resp, strlen(resp), 0, (struct sockaddr*)&client_addr, client_len); if (send_len == -1) { perror("发送响应失败"); continue; } printf("给%s:%d发送响应:%s\n", client_ip, client_port, resp); } // 4. 关闭套接字(循环不会退出,实际不会执行) close(sockfd); return 0; }4.3 完整代码:UDP 客户端
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define SERVER_IP "127.0.0.1" // 服务器IP(本地测试用127.0.0.1) #define SERVER_PORT 8888 // 服务器端口号 #define BUF_SIZE 1024 // 缓冲区大小 int main() { // 1. 创建UDP套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket创建失败"); exit(1); } printf("UDP套接字创建成功(fd=%d)\n", sockfd); // 2. 初始化服务器地址结构体 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; // IPv4 server_addr.sin_port = htons(SERVER_PORT); // 服务器端口转网络字节序 // 服务器IP转网络字节序 if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) { perror("服务器IP无效"); close(sockfd); exit(1); } // 3. 循环输入消息,发送给服务器,并接收响应 char buf[BUF_SIZE]; while (1) { // 输入要发送的消息 printf("请输入要发送的消息(输入q退出):"); fgets(buf, BUF_SIZE, stdin); // 去掉fgets自带的换行符 buf[strcspn(buf, "\n")] = '\0'; // 输入q退出 if (strcmp(buf, "q") == 0) { printf("客户端退出\n"); break; } // 发送消息给服务器 ssize_t send_len = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (send_len == -1) { perror("发送消息失败"); continue; } printf("发送给服务器(%s:%d)的消息:%s\n", SERVER_IP, SERVER_PORT, buf); // 接收服务器的响应 char resp[BUF_SIZE] = {0}; struct sockaddr_in server_resp_addr; // 存放服务器响应的地址(可忽略) socklen_t server_len = sizeof(server_resp_addr); ssize_t recv_len = recvfrom(sockfd, resp, BUF_SIZE-1, 0, (struct sockaddr*)&server_resp_addr, &server_len); if (recv_len == -1) { perror("接收响应失败"); continue; } resp[recv_len] = '\0'; printf("收到服务器的响应:%s\n", resp); } // 4. 关闭套接字 close(sockfd); return 0; }4.4 代码运行步骤
(1)编译代码
在 Linux/macOS 终端输入:
# 编译服务器 gcc udp_server.c -o udp_server # 编译客户端 gcc udp_client.c -o udp_client(2)启动服务器
./udp_server # 输出: # UDP套接字创建成功(fd=3) # 服务器绑定端口8888成功,等待客户端消息...(3)启动客户端(新终端)
./udp_client # 输出: # UDP套接字创建成功(fd=3) # 请输入要发送的消息(输入q退出):Hello UDP!(4)查看通信效果
- 客户端输入
Hello UDP!后,会显示:plaintext
发送给服务器(127.0.0.1:8888)的消息:Hello UDP! 收到服务器的响应:收到了:Hello UDP! - 服务器会显示:
plaintext
收到来自127.0.0.1:56789的消息:Hello UDP! 给127.0.0.1:56789发送响应:收到了:Hello UDP!
(注:客户端的端口号 56789 是系统动态分配的,每次启动可能不同)
五、总结
这篇博客从传输层的基础讲起,带你掌握了两个核心知识点:
- 端口号:传输层的 “门牌号”,用 16 位整数标识应用程序,五元组唯一确定一次通信,知名端口号是公共服务的固定端口;
- UDP 协议:传输层的 “快速快递”,无连接、不可靠、面向数据报,适合实时场景,核心函数是
socket、bind、sendto、recvfrom。
---------------------------------------------------------------------------------------------------------------------------------
通过实战代码,你亲手实现了 UDP 的客户端和服务器,理解了 UDP 通信的完整流程。下一篇我们会讲传输层的另一个核心协议 ——TCP,它的 “可靠传输” 特性是如何实现的,以及和 UDP 的区别。