JavaEE初阶教程之UDP协议和TCP协议
作者:学无止尽5

引言
传输层是网络通信的核心枢纽,TCP(传输控制协议)与 UDP(用户数据报协议)作为该层的两大基石,以差异化设计支撑多样化数据传输需求。
TCP 秉持 “可靠性优先”,通过面向连接、重传机制、流量与拥塞控制,实现数据无丢失、无差错、按序交付,成为文件传输、网页浏览等场景的核心支撑。
UDP 坚守 “高效轻便” 原则,采用无连接模式,省去连接建立与释放开销,以低延迟优势适配实时音视频、网络游戏等对响应速度敏感的场景。
二者优势互补,共同构成现代网络通信的基础,理解其特性是构建高效网络应用的关键。
一:UDP的协议
特点:UDP传输的过程类似于寄信
- 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接。
- 不可靠:没有确认机制,没有重传机制;如果因为网络故障导致该断无法发送对方,UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报:不能够灵活的控制读写数据的次数和数量。
===》:应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并;
例如:用UDP传输100个字节的数据:
如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接受100个字节,而不能循环调用10次recvfrom,每次接受10个字节。
UDP协议端格式

- 16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度;
- 如果校验和出错,就会直接丢弃;
UDP使用注意事项:
我们注意到, UDP协议首部中有一个16位的最大长度. 也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部),然而64K在当今的互联网环境下, 是一个非常小的数字。
如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装;
基于UDP的应用层协议:
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
也包括我们自己写UDP程序自定义的应用层协议
二:TCP协议
🔹 TCP全称 传输控制协议(Transmission Control Protocol),人如其名,聚焦数据传输的全流程精准控制,核心在于“可靠”与“有序”。
TCP协议端格式

🔹源/目的端口号:表示数据是从哪个进程来,到哪个进程去;
🔹32位序号/32位确认号:32位序号标数据,确认号应答已收;
🔹4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少4个字节);所以TCP头部的最大长度为15*4=60;
🔹6位标志位:
- URG:紧急指针是否有效
- ACK:确认序号是否有效
- PSH:提示接收端应用程序尽快从TCP缓冲区把数据读走
- RST:对方要求重新建立连接,我们把携带RST标识的称为复位报文段
- SYN:请求建立连接,我们把携带SYN标识的称之为同步报文段
- FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
🔹16位窗口大小:TCP 流量控制核心,限定接收端缓存容量
🔹16位校验和:发送端填充,CRC校验,接收端校验不通过,则认为数据有问题,此处的校验和不光包含TCP首部,也包含TCP数据部分;
🔹16位紧急指针:标识哪部分数据是紧急数据;
🔹40字节头部选项:TCP 头部可选字段,最大 40 字节扩展功能
下面介绍TCP的核心特性:
2.1:确认应答

TCP将每个字节数据都进行了编号,即为序列号。

每一个ACK都带有一个对应的确认信号,目的是为了告诉发送者,我已经收到了哪些数据,下次该从哪里发送;
2.2:延时重传

场景: 主机A发送数据给主机B后,可能因为网络拥堵等信息,数据无法到达主机B;
- 如果主机A在特定的时间间隔内,没有收到主机B发来的确认应答就会进行重发;(但是主机A未收到主机B的确认应答,也可能是因为ACK丢包了)

这样主机B就会收到很多重复的数据,那么TCP协议就需要能够识别出那些包是重复的包,并且把重复的丢弃掉;
那么是如何解决的呢?
这个时候就用上了我们的序列号,这样就可以很容易的实现去重的效果;
这里还有一个问题:超时时间该如何确定?
===》
- 最理想的情况下,找到一个最小的时间,保证确认应答能够在这个时间内可以返回。
- 随着网络的的波动,这个时间的长短是有差异的。
- 如果超时时间设置的太长,可能会影响整体的重传效率。
- 如果超时时间设置的太短,可能会频繁的发送重复的包。
⭐TCP为了保证无论在什么环境下都有比较高性能的通信,因此会动态的计算这个最大的超时时间。
- Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
- 如果重发一次后,还没有收到应答,等待2*500ms后再进行重传。
- 如果仍然还没有收到应答,等待4*500ms进行重传,依次类推,以指数的形式递增。
- 累计到了一定的重传次数,TCP认为网络或者对端主机出现异常,会强制关闭连接。
2.3:连接管理
这里最重要的点三次握手和四次挥手

TCP中的握手,传输一个“打招呼”的数据包,这个数据包不携带任何的业务数据(没有应用层载荷),数据包中只有报头,没有正文。
三次握手,在建立连接的过程中,客户端和服务器要经过三次这样的“打招呼”,连接才能在双方这边建立好。

1):客户端给服务器发起一个syn(同步报文段)客户端告诉服务器,我要和你进行连接,请你保存为的信息。
2):服务器给客户端返回一个ack,服务器告诉客户端:收到我会保存!!!
3):服务器也给客户端发送一个syn,告诉客户端,我也要和你建立连接,请你也保存我的信息
4):客户端收到之后,也会返回一个ack,告诉服务器:收到 ~我会保存
从逻辑上是四次交互,但是中间两次,可以合并成一个TCP数据包,网络上实际只传输了三个数据包。 实际图如下所示:

🏀那么三次握手(建立连接)有什么意义?
1.类比于投石问路,确认当前通信路径是否通畅
2.也是验证通信双方发送能力和接受能力是否正常
3.协商参数,通信双方共同确认一些通信中的必备的参数数值
LISTEN状态:服务器在
new ServerSocket就会进入到LISTEN表示在监听这个窗口,端口上过来的客户端就可以accept。
ESTABUSHED:客户端和服务器之间连接建立好了,可以进行通信了。

我们可以使用netstat -ano来查看状态。
TCP断开连接(四次挥手)

1):客户端告诉服务器,我要和你断开连接,请你把我删了;
2):服务器回应收到;
3):服务器也告诉客户端我也和你断开连接,请你也把我删了;
4):客户端回应收到;
客户端和服务器都会把对方的信息(之前保存对方的ip和端口)删除掉,删除完了就断开连接了;
之所以叫做四次挥手,是因为中间两次相互,是不一定触发合并的;
而三次握手本质区别在于四次挥手不是完全由操作系统内核完成的,而是和应用层代码有关系。合并场景是特殊情况,优化手段,所以还是把TCP的断开连接称为“四次挥手”;
那么合并的情况是什么?
比如,收到FIN触发close之间,间隔的时间本身就不长,再加上上一个ack延迟应答了,这时就可以把这两数据合并了;
2.3.1:CLOSE_WAIT:
被动一方断开连接进入的状态,收到对方发来的FIN,就会返回ACK同时进入CLOSE_WAIT状态。(可以理解成wait close,等待关闭,等待应用程序代码,调用close),通常情况下存在时间比较短。
一般而言,对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题.
通过cmd输入
netstat -an可以来查看这个状态。
2.3.2:TIME_WAIT:
(类似于线程中TIME_WAITING状态,有时间限制的等待),主动发起断开连接一方进入的状态,我方发起FIN,对方返回ACK,对方发起FIN,我方返回ACK同时进入TIME_WAIT状态 。
想⼀想, 为什么是TIME_WAIT的时间是2MSL?
- MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL的话
- 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自一个进程的迟到的数据, 但是这种数据很可能是错误的);
- 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重新再发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK);
其他几个关键的状态:
LISTEN:手机开机,信号良好,可以随时打电话。
ESTABISHED:连接已经建立,电话接听,可以说话了。
TCP的状态转换汇总:

2.4:滑动窗口
如果我们对每一个发送的数据段,都给一个ACK确认应答,收到了ACK在发送下一个数据段,这样就会造成问题—性能比较差,特别是数据返回时间很长的时候。

面对性能比较低,为我们可以一次发送多条数据,这样就可以大大提升性能(实际就是将多个段的等待时间重叠在一起了)。

当我们批量发送多少数据不需要等待的,就称之为“窗口大小”,当收到一个ACK时候就在发送一组数据,窗口再往后移动一格,此时这个窗口一直在往右平移,即我们就称之为“滑动窗口”。

操作系统为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据还有应答,只有确认应答过的数据,才能从缓冲区删掉。
这里的窗口越大,即网络的吞吐率越高。
那么如果这里出现了丢包—ACK丢了,怎么进行重传的?

这种情况的丢包没啥大要紧关系,因为可以通过后面的ACK进行确认。即当1001这个ACK返回的丢包了,可以通过收到2001这个ACK来确认接收方收到了1-1000的数据。
但是如果数据包丢了。

- 当1001-2000报文段丢失后,发送端就会一直收到1001这样的ACK
- 如果发送端连续三次收到了同样一个1001这样的应答,就会将数据1001-2000重新发送;
- 这个时候接收端收到1001之后,再次返回的ACK就是7001(这是因为2001-7000)接收端已经就已经收到了,被放到了接收端操作系统内核的接收缓冲区里面了;
这种机制被称之为“高速重发控制”(也叫“快重传”);
2.5:流量控制
接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区满了,这个时候发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。所以TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control);
- 接受端将自己可以接受的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK端通知发送端;
- 窗口大小字段越大,说明网络吞吐量越高;
- 当接收端发现自己的缓冲区要满了,就会将窗口大小设置成一个更小的值通知发送给发送端;
- 发送端接收到这个窗口后,就会减慢自己的发送速度;
- 如果接收端缓冲区满了,就会将窗口大小设置为0,这时发送方不在发送数据,但是需要定期发送一个窗口探测数据段,把接收端窗口大小告诉给发送端;

这里TCP首部有一个16位的窗口字段,就是存放了窗口大小信息;这里有一个问题16位数字最大表示是65535,那么TCP的窗口大小最大也是65535字节吗?实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口的大小是窗口字段的值左移M位;
2.6:拥塞控制
虽然TCP有了滑动窗口这个神器,能够高效可靠的发送大量的数据,但是如果在刚开始阶段就发送大量的数据,可能会引发问题。因为网络上有很多计算机,看你当前的网络就是已经比较拥堵,在不清楚当前网络的状态下,贸然发送大量数据,会引起问题。
TCP引入慢启动机制,先发送少量数据,先探探路在摸清楚当前网络的拥堵状态,再决定按照多大的速度传输数据。

此时我们的拥塞窗口就起到了作用,刚开始发送,定义拥塞窗口大小为1,每次收到一个ACK应答,拥塞窗口就加1,每次发送数据的时候,将拥塞窗口和接收端主机反馈的窗口大小作比较,取较小的值作为实际发送端的窗口大小;
拥塞窗口增长速度是指数级别的,慢启动只是初始时候慢,其增长速度是非常快的。
为了使增长的速度不那么快,就不能是拥塞窗口单纯的加倍;我们引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候,就不再按照指数方式增长,而是按照线性方式增长;当TCP启动的时候慢启动阈值等于窗口最大值,在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口的值置为1;少量的丢包,我们认为是出发超时重传,大量的丢包,我们就认为网络拥塞;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立即下降;

拥塞控制归根结底是TCP协议想尽可能快的把数据传输给对方,但又要避免给网络造成太大的压力的折中方案。
2.7:延时应答
承接滑动窗口,在可靠性的前提下尽量让窗口大一些,最终让传输效率提高一些。不立即返回ACK。而是稍微等一等,就相当于给接收方留出一些时间,好能够多消费一些,使接收缓冲区的剩余空间更大一点!
如果接收数据的主机立刻返回ACK应答,此时返回的窗口可能偏小。举个直观例子:
- 假设接收端缓冲区总大小为1M,一次收到500K数据后立刻应答,返回的窗口仅为500K;
- 但实际处理端可能速度很快,10ms内就把这500K数据从缓冲区消费完毕;
- 这种情况下,接收端远没达到处理极限,就算窗口再放大也能应对。
如果接收端稍微延迟一点应答(比如等待200ms),此时缓冲区已空闲,返回的窗口大小就能达到1M。要知道:窗口越大,网络吞吐量越高,传输效率也越强。我们的核心目标,就是在避免网络拥塞的前提下,最大化传输效率。
不过,并非所有包都能延迟应答,需要遵守两个关键限制:
- 数量限制:每接收N个包,必须应答一次;
- 时间限制:超过最大延迟时间,必须应答一次。
具体的N值和超时时间,不同操作系统会有差异,通常N取2,超时时间默认200ms。

2.8:捎带应答
网络通信中,经常是“一问一答的模型”,客户端发起request,服务器返回response

由于TCP会延时应答,当推迟后就正好赶上服务器返回的响应了,这样就可以直接把ACK报文和响应数据,做成一个tcp数据报返回给客户端了。此时基于延时应答的基础上,提高传输效率的方案==》就叫做捎带应答

捎带应答,既取决于延时应答,有和应用程序的处理逻辑有关,捎带应答不是100%会触发的,但TCP会尽可能的进行捎带应答。
2.9:面向字节流
创建TCP socket时,内核会同步生成发送缓冲区与接收缓冲区,二者是TCP数据传输的核心中间载体,支撑起连接的全双工特性。
数据写入流程:调用write时,数据并非直接发送至网络,而是先写入发送缓冲区。若数据量过大,内核会自动拆分為多个TCP数据包分批发送;若数据量过小,则会在缓冲区暂存,等待达到合适大小或满足触发条件后再批量发送,以此提升传输效率。
数据读取流程:接收方的数据包先由网卡驱动接收,再传入内核的接收缓冲区,应用程序通过调用read,即可从该缓冲区中获取数据。
缓冲区的存在让TCP的读写操作完全解耦,无需一一匹配:
- 写入100字节时,可一次调用
write写入全部,也可分100次调用、每次写1字节; - 读取100字节时,无需关注发送方的写入方式,既可一次
read读取全部,也可分100次、每次读1字节。
正是这两个独立缓冲区的存在,使得单个TCP连接能同时进行读、写操作,这一特性被称为全双工。
2.10:粘包问题
以下面例子为例:

去掉报头后,把载荷内容梵高一个接收缓冲区里面;

则read的可能性:
1):a a a b b b c c c
2):aa ab bb ccc
3):aaa bbb ccc
4):aaab bbcc c
5):aa abb bcc c
6):aaa bbbc cc
··········等等
粘包,粘的是应用层的数据包
由于TCP字节流的特性,收到多个TCP数据报的时候把所有的载荷都混到了一起放到接收缓冲区里面。
那么如何解决粘包问题:
- 通过特殊的分隔符,来作为包边界的区分。(比如:约定每个应用层数据包都以 ;结尾)但是这里要注意的是包里面内容不能包括 ;这个字符,所以需要找到合适的分隔符,确保这个符号不会再正文中重复出现;
- 在应用层数据包开头的地方,通过固定长度,约定整个应用层数据包的长度。


应用程序在read的时候,先固定read2个字节,看看2个字节里面的内容是啥?(发现是3接下来在read3个字节,就读到了aaa这样就是完整的应用层数据保包了)。
粘包问题只针对于字节流的传输,对于文件操作(使用文件存储多个结构化数据,也是可能涉及到粘包问题的)例如:
通过文件,保存若干学生信息
//简写代码
class Student{
name;
id;
age;
classId;
···
}
此时约定成每个学生信息占一行使用 \n 作为结束标记即分隔符;
对于UDP来说,就不存在粘包问题;


这里应用层每次调用recv得到的就是一个完整的DatagramPacket ,也就对应一个完整应用层数据包;
2.11:异常情况
- 1.进程崩溃
意味着对应的文件描述符就被关闭了(调用close,关闭进程)只要进程退出了,都会释放PCB,释放文件描述表。
- 2.主机关机(正常流程)
正常流程下主机关机,就会先杀死所有的进程,此时也会触发FIN,进而进入四次挥手;如果关机速度比较慢,有很大的可能四次挥手挥完了;如果关机速度表较快,四次挥手就会没有挥完刚发FIN,机器就关闭了; 对端可以正常返回ACK,也会继续正常发送FIN,这里的FIN就没有收到ACK,尝试重传几次FIN;还是没有收到ACK,对端就会直接断开连接即删除之前保存的对端信息。
- 3.主机掉电(直接拔掉电源)
这里就会来不及发起FIN
1):如果掉电的一方是接收方,对方是发送方。对方会继续发送数据,没有ack就会超时重传在还没有ack继续超时重传;到了一定的程度后,发送方就会发送一个复位报文 表示放弃链接。
2):如果掉电一方是发送方,对方是接收方;这里就引入了一个心跳包 :接收方会周期性的和发送方交换“心跳包”;
A给B发送一个无业务数据的报文,B就给A返回一个ACK。如果对方有应答就表示对方是正常工作的;如果心跳包没有应答,就可以认为对方挂了,这时候就可以单方面的释放连接了;
- 网线断开
对于是发送方来说,没有ack=》超时重传=》仍然没有ack=》超时重传=》达到一定程度,放弃连接;
对于是接收方来说,周期性出发心跳包=》发现对端下线=》放弃连接;
三:UDP和TCP的区别
虽然TCP是可靠连接,但不能简单判定其绝对优于UDP——二者作为TCP/IP协议族的核心传输层协议,优缺点各有侧重,需结合具体场景选择,而非绝对化比较。
TCP的核心优势是可靠传输:通过三次握手建立连接、四次挥手关闭连接,搭配重传、排序、流量控制、拥塞控制等机制,能确保数据无丢失、无重复、按序到达。其适用场景集中在对数据完整性要求高的场景,比如文件传输、重要数据同步、支付转账、状态更新等,缺点是协议开销大、传输延迟较高,且不支持广播/组播。
UDP的核心优势是高效实时:无需建立连接,头部开销极小,数据发送直接高效,延迟低、吞吐量高,且支持广播和组播。它适用于对实时性、传输速度要求优先于可靠性的场景,比如早期QQ聊天、视频/语音通话、直播、游戏数据传输等,缺点是无可靠传输保障,数据可能丢失、乱序,需应用层自行处理可靠性需求。
归根结底,TCP和UDP是程序员应对不同需求的工具,选择的核心是匹配场景诉求:追求可靠选TCP,追求高效实时选UDP。
最后再分享一道经典面试题给大家哈!----》用UDP实现可靠传输!
我们可以参考TCP的可靠性机制,在应用层实现类似的逻辑!
如:引入序列号,保证数据顺序;
引入确认应答,确保对端收到了数据;
引入超时重传,如果隔一段时间没有收到应答,就重新发送数据
引入滑动窗口,设定发送窗口大小,无需等待前一个包的 ACK 即可发送后续包,避免 “发一个等一个” 的低效问题,平衡可靠性与传输效率。
以及流量控制和拥塞控制;
总结
到此这篇关于JavaEE初阶教程之UDP协议和TCP协议的文章就介绍到这了,更多相关Java UDP协议和TCP协议内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
