news 2026/8/5 14:52:51

计算机网络_UDP和TCP

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
计算机网络_UDP和TCP

TCP(传输控制协议)和 UDP(用户数据报协议)是TCP/IP 协议簇中传输层的两大核心协议,它们的设计理念完全相反 ——TCP 追求可靠传输,UDP 追求高效传输,这直接决定了它们在不同业务场景(尤其是游戏开发)中的选型差异。下面从协议本质、核心机制、详细区别、适用场景四个维度,给你讲透两者的差异。

一、协议本质与核心设计目标

传输层的核心作用是“端到端” 的数据传输(从一台主机的应用程序,传到另一台主机的应用程序),而 TCP 和 UDP 的目标完全不同:

  • TCP:设计目标是“保证数据 100% 准确、有序地到达目的地”,哪怕牺牲传输速度。可以类比成“挂号信”:寄件前要确认收件人地址有效(建立连接),寄件后要等收件人签收(确认应答),丢件了要重寄(重传机制),全程可追踪。
  • UDP:设计目标是“以最快速度把数据发出去,不保证是否到达”,哪怕牺牲可靠性。可以类比成“明信片”:写好地址直接投递,不用确认收件人是否收到,丢了也不补寄,速度快、开销小。

二、TCP 详解:面向连接的可靠传输协议

TCP 是面向连接、可靠、基于字节流的协议,所有复杂机制都是为了实现 “可靠传输” 这个核心目标。

1. 核心机制(可靠传输的底层逻辑)

SYN (Synchronize Sequence Numbers)
  • 中文意思同步序列号

  • 作用发起连接

  • 场景:当一台计算机想要和另一台计算机建立连接时,它发送的第一个包必须带有 SYN 标志。

  • 比喻:相当于打电话时的“喂?在吗?”或者敲门声。它告诉对方:“我想和你建立连接,咱们来对一下暗号(同步一下序列号),方便后面传输数据。”

ACK (Acknowledgment)
  • 中文意思确认

  • 作用回复/确认收到

  • 场景:TCP 是可靠传输,所以每收到一个关键数据包,都必须给对方回复一个 ACK,告诉对方“我收到了”。

  • 比喻:相当于对话中的“嗯”“收到了”“听清楚了”。如果发送方没收到 ACK,它会认为你没听见,就会重新发一遍。

FIN (Finish)
  • 中文意思结束

  • 作用断开连接

  • 场景:当一方的数据发送完毕,想要断开连接时,会发送带有 FIN 标志的包。

  • 比喻:相当于打电话结束时说的“我说完了”“挂了啊”

(1)面向连接:三次握手建立连接,四次挥手断开连接

TCP 通信前必须先建立双向连接,通信后必须优雅断开连接,这个过程是可靠传输的基础。

  • 三次握手(建立连接)
    1. 客户端 → 服务端:发送SYN包,请求建立连接;
      “你好,B,我想跟你建立连接。”
    2. 服务端 → 客户端:发送SYN+ACK包,确认收到请求,并同意建立连接;
      “收到你的请求了(ACK),我也想跟你建立连接(SYN)。”
    3. 客户端 → 服务端:发送ACK包,确认收到服务端的同意,连接正式建立。
      “收到你的回复了,那咱们连接建好了。”
      作用:确保双方的收发能力都正常,避免 “单边连接” 导致的数据丢失。
  • 四次挥手(断开连接)
    1. 主动方 → 被动方:发送FIN包,请求断开连接;
      “B,我的数据发完了,我想断开连接。”
    2. 被动方 → 主动方:发送ACK包,确认收到断开请求;
      “知道了(ACK)。但我这边可能还有点数据没发完,你先等会儿。”
    3. 被动方 → 主动方:发送FIN包,告知自己的数据已发完,准备断开;
      “好了 A,我这边也都发完了,现在可以彻底断开了。”
    4. 主动方 → 被动方:发送ACK包,确认断开,连接正式关闭。
      “好的,拜拜。”
      作用:确保双方的数据都传输完毕,避免断开时丢包。
(2)可靠传输:序列号 + 确认应答 + 重传机制

这是 TCP 最核心的 “可靠性保障”,解决了丢包、重复、乱序三大问题:

  • 序列号:每个字节的数据都有唯一的序列号,接收方可以通过序列号判断数据是否乱序、是否重复。
  • 确认应答(ACK):接收方收到数据后,会发送一个ACK包,告诉发送方 “我已经收到了序列号 X 之前的所有数据”。
  • 重传机制:如果发送方在超时时间内没收到ACK,就认为数据丢了,会自动重传:
    • 超时重传:等待超时后重传(通用场景);
    • 快速重传:如果收到 3 个重复的ACK,直接重传(不用等超时,效率更高)。
(3)流量控制 + 拥塞控制:避免网络过载
  • 流量控制:通过滑动窗口实现,接收方会告诉发送方 “我当前能接收的最大数据量”,发送方不会超过这个量,避免接收方处理不过来导致丢包。
  • 拥塞控制:TCP 会根据网络状况动态调整发送速率(比如慢启动、拥塞避免、快速恢复),防止大量数据涌入网络导致拥堵。
(4)基于字节流:无数据边界,可能出现粘包

TCP 把数据当成连续的字节流传输,没有 “数据包边界” 的概念。

  • 比如发送方分 3 次发ABC,接收方可能一次收到ABC,也可能分两次收到AB+C—— 这就是粘包问题,需要应用层自己定义 “分隔符” 或 “长度字段” 来解决。

2. TCP 头部结构(开销大)

TCP 头部至少 20 字节,最多 60 字节(可选字段),包含序列号、确认号、窗口大小、标志位(SYN/ACK/FIN 等)等关键信息 —— 这些字段是实现可靠传输的基础,但也带来了额外的网络开销。

三、UDP 详解:无连接的高效传输协议

UDP 是无连接、不可靠、基于数据报的协议,完全抛弃了 TCP 的复杂机制,只保留最核心的 “发送数据” 功能。

1. 核心特性(高效传输的底层逻辑)

(1)无连接:无需握手,直接发送

UDP 通信前不需要建立连接,发送方不管接收方是否在线、是否准备好,直接把数据封装成 “数据报” 发出去;通信后也不需要断开连接。

  • 优点:省去了三次握手 / 四次挥手的时间,延迟极低
  • 缺点:无法确认接收方是否存在,数据丢了也不知道。
(2)不可靠传输:无确认、无重传、无排序

UDP 没有序列号、ACK 包、重传机制,数据一旦发出去,就 “生死由命”:

  • 数据可能丢包:网络拥堵时,路由器会优先丢弃 UDP 包;
  • 数据可能乱序:后发的包可能先到,接收方不会自动排序;
  • 数据可能重复:同一个包可能被多次接收。
(3)基于数据报:有边界,不会粘包

UDP 把数据当成独立的数据包传输,每个数据报都有明确的边界:

  • 发送方发一个 100 字节的包,接收方要么完整收到 100 字节,要么完全收不到 ——不会出现粘包问题,应用层处理更简单。
(4)支持广播 / 组播:一对多传输

UDP 支持广播(发给同一网段的所有主机)和组播(发给特定组的主机),而 TCP 只能一对一传输。这一特性让 UDP 很适合游戏的 “房间广播”“服务器公告” 等场景。

2. UDP 头部结构(开销极小)

UDP 头部只有 8 字节,只包含 4 个字段:源端口、目的端口、数据长度、校验和 —— 没有任何额外控制字段,传输效率极高。

四、TCP 与 UDP 核心区别详细对比表

对比维度TCPUDP关键差异解读
连接性面向连接无连接TCP 必须三次握手建立连接,UDP 直接发数据,延迟更低
可靠性可靠传输不可靠传输TCP 保证数据不丢、不重、有序;UDP 不保证,丢包不重传
传输单位字节流(无边界)数据报(有边界)TCP 可能粘包,需应用层处理;UDP 不会粘包,直接按包接收
头部开销20~60 字节8 字节TCP 头部字段多,开销大;UDP 极简,开销小
确认机制确认应答(ACK)无确认应答TCP 收到数据必回 ACK,UDP 发完不管
重传机制超时重传 + 快速重传无重传机制TCP 丢包会重传,UDP 丢包就丢了
流量控制支持(滑动窗口)不支持TCP 会根据接收方能力调整速率,UDP 无脑发
拥塞控制支持(慢启动等)不支持TCP 会根据网络状况降速,UDP 可能加剧网络拥堵
通信方式一对一一对一 / 一对多(广播 / 组播)UDP 适合群发,TCP 只能点对点
延迟 / 效率延迟高,效率低延迟低,效率高TCP 机制复杂,UDP 极简,实时性更好

五、适用场景(结合游戏开发重点说明)

TCP 和 UDP 没有绝对的优劣,只有场景适配性,游戏开发中通常会两者混用

1. TCP 的适用场景:数据不能丢,准确性优先

适合低频、关键、对实时性要求不高的数据传输,比如:

  • 游戏登录验证、账号密码传输、角色注册;
  • 道具交易、金币充值、存档同步(这些数据丢了会导致用户权益受损);
  • 游戏公告、邮件推送(不需要实时到达,但必须准确到达)。

2. UDP 的适用场景:实时性优先,允许少量丢包

适合高频、实时、对可靠性要求不高的数据传输,比如:

  • 多人游戏的角色移动、视角转动、技能释放(哪怕丢 1-2 个包,后续的新位置包也能补上,不会影响体验);
  • 实时语音聊天、游戏内的实时弹幕;
  • 房间内的玩家状态广播(比如 “玩家 A 进入房间”)。

3. 游戏开发的最佳实践:TCP + UDP 混用

几乎所有大型多人在线游戏(MMO)都采用这种模式:

  • UDP 处理实时性强的高频数据(角色移动、战斗同步),保证游戏流畅;
  • TCP 处理关键的低频数据(登录、交易、存档),保证数据准确;
  • 基于 UDP 自己实现 “轻量级可靠性”:比如对关键的 UDP 包(如技能释放指令),手动加确认和重传机制,兼顾实时性和可靠性(虚幻引擎的NetDriver就有类似设计)。

六、案例

UDP:

Server:Linux

//UdpServer.cc #include <iostream> #include <string> #include <memory> #include <cerrno> #include <cstring> #include <unistd.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> const static uint16_t defaultport = 8888; const static int defaultfd = -1; const static int defaultsize = 1024; enum { Usage_Err = 1, Socket_Err, Bind_Err }; class UdpServer { public: UdpServer(uint16_t port = defaultport) : _port(port), _sockfd(defaultfd) { } void Init() { // 1. 创建socket,就是创建了文件细节 _sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd < 0) { exit(Socket_Err); } // 2. 绑定,指定网络信息 struct sockaddr_in local; bzero(&local, sizeof(local)); // memset local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = INADDR_ANY; // 1. 4字节IP 2. 变成网络序列 // 结构体填完,设置到内核中了吗??没有 int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); if (n != 0) { exit(Bind_Err); } } void Start() { // 服务器永远不退出 char buffer[defaultsize]; for (;;) { struct sockaddr_in peer; socklen_t len = sizeof(peer); // 不能乱写 ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len); if (n > 0) { uint16_t clientport = ntohs(peer.sin_port); std::string clientip = inet_ntoa(peer.sin_addr); std::string prefix = clientip + ":" + std::to_string(clientport); buffer[n] = 0; std::cout << prefix << "# " << buffer << std::endl; std::string echo = buffer; echo += "[udp server echo message]"; sendto(_sockfd, echo.c_str(), echo.size(), 0, (struct sockaddr *)&peer, len); } } } ~UdpServer() { } private: uint16_t _port; int _sockfd; }; void Usage(std::string proc) { std::cout << "Usage : \n\t" << proc << " local_port\n" << std::endl; } // ./udp_server 8888 int main(int argc, char *argv[]) { if (argc != 2) { Usage(argv[0]); return Usage_Err; } uint16_t port = std::stoi(argv[1]); std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port); usvr->Init(); usvr->Start(); return 0; }

Client:Windows

//UdpClient.cc #include <iostream> #include <cstdio> #include <thread> #include <string> #include <cstdlib> #include <WinSock2.h> #include <Windows.h> #pragma warning(disable : 4996) #pragma comment(lib, "ws2_32.lib") std::string serverip = ""; // 填写你的云服务器ip uint16_t serverport = 8888; // 填写你的云服务开放的端口号 int main() { WSADATA wsd; WSAStartup(MAKEWORD(2, 2), &wsd); struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverport); //? server.sin_addr.s_addr = inet_addr(serverip.c_str()); SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == SOCKET_ERROR) { std::cout << "socker error" << std::endl; return 1; } std::string message; char buffer[1024]; while (true) { std::cout << "Please Enter@ "; std::getline(std::cin, message); if(message.empty()) continue; sendto(sockfd, message.c_str(), (int)message.size(), 0, (struct sockaddr *)&server, sizeof(server)); struct sockaddr_in temp; int len = sizeof(temp); int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len); if (s > 0) { buffer[s] = 0; std::cout << buffer << std::endl; } } closesocket(sockfd); WSACleanup(); return 0; }

TCP:

Sever:Linux

//TcpClient.cc #include <winsock2.h> #include <iostream> #include <string> #pragma warning(disable : 4996) #pragma comment(lib, "ws2_32.lib") std::string serverip = ""; // 填写你的云服务器ip uint16_t serverport = 8888; // 填写你的云服务开放的端口号 int main() { WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { std::cerr << "WSAStartup failed: " << result << std::endl; return 1; } SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (clientSocket == INVALID_SOCKET) { std::cerr << "socket failed" << std::endl; WSACleanup(); return 1; } sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(serverport); // 替换为服务器端口 serverAddr.sin_addr.s_addr = inet_addr(serverip.c_str()); // 替换为服务器IP地址 result = connect(clientSocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr)); if (result == SOCKET_ERROR) { std::cerr << "connect failed" << std::endl; closesocket(clientSocket); WSACleanup(); return 1; } while (true) { std::string message; std::cout << "Please Enter@ "; std::getline(std::cin, message); if(message.empty()) continue; send(clientSocket, message.c_str(), message.size(), 0); char buffer[1024] = {0}; int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0); if (bytesReceived > 0) { buffer[bytesReceived] = '\0'; // 确保字符串以 null 结尾 std::cout << "Received from server: " << buffer << std::endl; } else { std::cerr << "recv failed" << std::endl; } } closesocket(clientSocket); WSACleanup(); return 0; }

Client:Windows

//TcpServer.cc #include <iostream> #include <string> #include <cerrno> #include <cstring> #include <cstdlib> #include <memory> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #include <unistd.h> const static int default_backlog = 6; enum { Usage_Err = 1, Socket_Err, Bind_Err, Listen_Err }; #define CONV(addr_ptr) ((struct sockaddr *)addr_ptr) class TcpServer { public: TcpServer(uint16_t port) : _port(port), _isrunning(false) { } // 都是固定套路 void Init() { // 1. 创建socket, file fd, 本质是文件 _listensock = socket(AF_INET, SOCK_STREAM, 0); if (_listensock < 0) { exit(0); } int opt = 1; setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 2. 填充本地网络信息并bind struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = htonl(INADDR_ANY); // 2.1 bind if (bind(_listensock, CONV(&local), sizeof(local)) != 0) { exit(Bind_Err); } // 3. 设置socket为监听状态,tcp特有的 if (listen(_listensock, default_backlog) != 0) { exit(Listen_Err); } } void ProcessConnection(int sockfd, struct sockaddr_in &peer) { uint16_t clientport = ntohs(peer.sin_port); std::string clientip = inet_ntoa(peer.sin_addr); std::string prefix = clientip + ":" + std::to_string(clientport); std::cout << "get a new connection, info is : " << prefix << std::endl; while (true) { char inbuffer[1024]; ssize_t s = ::read(sockfd, inbuffer, sizeof(inbuffer)-1); if(s > 0) { inbuffer[s] = 0; std::cout << prefix << "# " << inbuffer << std::endl; std::string echo = inbuffer; echo += "[tcp server echo message]"; write(sockfd, echo.c_str(), echo.size()); } else { std::cout << prefix << " client quit" << std::endl; break; } } } void Start() { _isrunning = true; while (_isrunning) { // 4. 获取连接 struct sockaddr_in peer; socklen_t len = sizeof(peer); int sockfd = accept(_listensock, CONV(&peer), &len); if (sockfd < 0) { continue; } ProcessConnection(sockfd, peer); } } ~TcpServer() { } private: uint16_t _port; int _listensock; // TODO bool _isrunning; }; using namespace std; void Usage(std::string proc) { std::cout << "Usage : \n\t" << proc << " local_port\n" << std::endl; } // ./tcp_server 8888 int main(int argc, char *argv[]) { if (argc != 2) { Usage(argv[0]); return Usage_Err; } uint16_t port = stoi(argv[1]); std::unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port); tsvr->Init(); tsvr->Start(); return 0; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/5 14:48:01

影刀RPA蓝印专业版打包EXE功能详解:从流程到独立程序

如果你正在考虑使用影刀RPA来自动化你的工作流程&#xff0c;但面对“个人版”、“专业版”、“企业版”这些选项时感到困惑&#xff0c;不知道哪个版本真正适合自己&#xff0c;那么这篇文章就是为你准备的。很多初次接触RPA的朋友&#xff0c;尤其是非技术背景的“普通人”&a…

作者头像 李华
网站建设 2026/8/5 14:47:46

如何免费解锁全网无损音乐:洛雪音乐音源终极配置指南

如何免费解锁全网无损音乐&#xff1a;洛雪音乐音源终极配置指南 【免费下载链接】lxmusic- lxmusic(洛雪音乐)全网最新最全音源 项目地址: https://gitcode.com/gh_mirrors/lx/lxmusic- 想要免费享受酷我、酷狗、QQ音乐、网易云等平台的FLAC无损音乐吗&#xff1f;洛雪…

作者头像 李华
网站建设 2026/8/5 14:47:01

从Hello World到.NET网站开发:深入理解C#程序结构与编译原理

1. 从“Hello World”看C#程序骨架&#xff1a;不止是打印一行字很多朋友在接触一门新语言时&#xff0c;第一个程序往往是“Hello World”。在黄菊华老师的C#、.NET网站开发教程里&#xff0c;这个看似简单的例子被放在了“程序结构”这一讲&#xff0c;这其实是一个非常重要的…

作者头像 李华
网站建设 2026/8/5 14:45:44

专业指南:如何高效获取国家中小学智慧教育平台电子课本PDF资源

专业指南&#xff1a;如何高效获取国家中小学智慧教育平台电子课本PDF资源 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具&#xff0c;帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载&#xff0c;让您更方便地获取课本内容。 …

作者头像 李华
网站建设 2026/8/5 14:44:06

免费开源小说阅读器ReadCat:终极纯净阅读体验完整指南

免费开源小说阅读器ReadCat&#xff1a;终极纯净阅读体验完整指南 【免费下载链接】read-cat 一款免费、开源、简洁、纯净、无广告的小说阅读器 项目地址: https://gitcode.com/gh_mirrors/re/read-cat 厌倦了广告弹窗满天飞的阅读软件&#xff1f;想要一个真正专注于阅…

作者头像 李华