TCP协议

TCP 全称为"传输控制协议( Transmission Control Protocol ").⼈如其名,要对数据的传输 进⾏⼀个详细的控制;

TCP协议段格式

TCP 报头字段说明

  • 源端口(16 位) 发送方应用程序的端口号。

  • 目的端口(16 位) 接收方应用程序的端口号。

  • 序列号(32 位) 本报文段数据第一个字节的序号,用于保证数据按序交付。

  • 确认号(32 位) 期望收到对方下一个字节的序号,代表该序号之前的数据都已正确接收。

  • 首部长度(4 位) 标识 TCP 首部总长度,单位为 4 字节,用来区分固定首部和可选项。

  • 保留位(6 位) 预留扩展位,当前固定为 0,暂未使用。

  • 控制标志位(6 位,每位独立生效)

    • URG:紧急指针有效位
    • ACK:确认号有效位,连接建立后报文必须置 1
    • PSH:推送位,要求接收方立刻把数据交给应用层
    • RST:复位位,用于强制中断异常连接
    • SYN:同步位,用于三次握手建立连接
    • FIN:结束位,用于四次挥手释放连接
  • 窗口大小(16 位) 告知对方己方的剩余接收缓存容量,用于实现流量控制。

  • 检验和(16 位) 校验整个 TCP 报文的传输正确性,检测数据是否损坏、出错。

  • 紧急指针(16 位) 配合 URG 位使用,标记紧急数据的结束位置,优先交付。

  • 可选项字段 长度不固定,必须为 32 位(4 字节)的整数倍,用于扩展功能(如最大报文段长度 MSS)。

TCP 连接的建立

TCP 建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个 TCP 报文段,称之为三报文握手,采用三报文握手主要是为了防止已失效的连接请求报文段突然又传送到了,因而产生错误。

TCP 的连接建立要解决以下三个问题: 1、使 TCP 双方能够确知对方的存在。 2、使 TCP 双方能够协商一些参数 3、使 TCP 双方能够对运输实体资源(例如缓存大小连接表中的项目等)进行分配。

假设客户端主动打开,服务器被动监听在某个端口,初始状态分别为CLOSEDLISTEN

第一次握手:客户端 → 服务器 SYN
  • 报文标志位:SYN=1, ACK=0
  • 序列号:客户端选择一个随机的 32 位初始序列号seq = x(称为 ISN)。
  • 报文可携带数据吗:不能。SYN 报文不包含应用数据,但会占用一个序列号。
  • 可携带选项:通常会附带 TCP 选项,如 MSS(最大报文段大小)、窗口缩放因子、时间戳、是否支持 SACK 等。
  • 客户端状态:由CLOSED进入SYN_SENT
  • 服务器状态:保持LISTEN状态不变。
  • 这个动作相当于客户端说:“我想和你同步,我的起始编号是 x。”
第二次握手:服务器 → 客户端 SYN+ACK
  • 报文标志位:SYN=1, ACK=1
  • 序列号:服务器选择一个随机的 32 位初始序列号seq = y(自身 ISN),确认号ack = x+1
  • 报文可携带数据吗:不能。该报文不包含应用数据,SYN 标识会占用一个序列号。
  • 可携带选项:可回应、协商客户端发来的 TCP 相关参数选项。
  • 服务器状态:由LISTEN进入SYN_RCVD
  • 客户端状态:保持SYN_SENT状态不变。
  • 这个动作相当于服务器说:“我收到你的同步请求了,认可你的起始编号 x;我的同步起始编号是 y。”
第三次握手:客户端 → 服务器 ACK
  • 报文标志位:ACK=1, SYN=0
  • 序列号:seq = x+1,确认号ack = y+1
  • 报文可携带数据吗:可以携带应用数据,无数据时仅作确认,不会占用新的序列号。
  • 可携带选项:一般不再新增协商选项,沿用此前协商好的参数。
  • 客户端状态:由SYN_SENT进入ESTABLISHED
  • 服务器状态:收到该报文后由SYN_RCVD进入ESTABLISHED
  • 这个动作相当于客户端说:“我收到你的同步信息了,双方连接正式建立,可以传输数据了。”

为什么要三次握手?

·以最短的方式进行验证全双工!本质是验证两个所处网络是通畅的,支持全双工!

·以最小成本100%确认双方通信意愿!

TCP 连接的关闭

四次挥手是 TCP 释放连接的过程,它比三次握手更复杂,因为 TCP 连接是全双工的,每个方向都必须单独关闭。理解四次挥手,就是理解 “优雅地结束一段双向通信” 这件事在不可靠网络上如何被安全实现。

假设客户端主动关闭,服务器被动关闭。初始状态均为ESTABLISHED

第一次挥手:主动关闭方 → 被动关闭方 FIN
  • 报文标志位:FIN=1,ACK 通常同时置 1
  • 序列号:seq = u,此处的 u 是主动关闭方之前发送的最后数据字节序号 + 1
  • 确认号:若携带 ACK,则用于确认对端已传输的数据
  • 含义:“我的数据已经发完了,我要关闭我这个方向的连接了,不会再发送数据了,但我还可以接收数据。”
  • 状态变迁:主动关闭方由 ESTABLISHED 进入 FIN_WAIT_1

FIN 段也占用一个序列号(u),和 SYN 一样,即使不承载数据。

第二次挥手:被动关闭方 → 主动关闭方 ACK
  • 报文标志位:ACK=1
  • 序列号:seq = v,被动关闭方的当前序列号
  • 确认号:ack = u+1,对应确认主动方的 FIN 报文
  • 含义:“我已收到你的关闭请求,先给你确认;但我这边可能还有未发送完的数据,暂不关闭本方向。”
  • 状态变迁:被动关闭方由 ESTABLISHED 进入 CLOSE_WAIT;主动关闭方收到该报文后,由 FIN_WAIT_1 进入 FIN_WAIT_2
第三次挥手:被动关闭方 → 主动关闭方 FIN
  • 报文标志位:FIN=1,ACK=1
  • 序列号:seq = w,被动关闭方所有数据发送完毕后的最终序列号
  • 确认号:ack = u+1,沿用此前的确认序号
  • 含义:“我这边的数据也全部发送完毕了,我这个方向也申请关闭。”
  • 状态变迁:被动关闭方由 CLOSE_WAIT 进入 LAST_ACK
第四次挥手:主动关闭方 → 被动关闭方 ACK
  • 报文标志位:ACK=1
  • 序列号:seq = u+1
  • 确认号:ack = w+1,对应确认被动方的 FIN 报文
  • 含义:“我已收到你的关闭通知,确认应答;连接可以正式释放。”
  • 状态变迁:主动关闭方发送报文后进入 TIME_WAIT 状态,等待 2MSL 后进入 CLOSED;被动关闭方收到该报文后,由 LAST_ACK 进入 CLOSED

为什么是四次挥手而不是三次?

核心原因是TCP 是全双工通信,两个传输方向需要独立关闭,被动方的确认和关闭请求通常无法合并发送

  • 挥手时,主动方发FIN只代表「我不再发送数据了」,但仍可以接收数据;被动方收到后必须先回ACK确认收到关闭请求。
  • 此时被动方可能还有未发送完的数据,不能立刻发FIN,需要等自身数据全部传输结束后,再发FIN告知「我也发完了,可以关闭」。
  • 握手时服务器的SYNACK可以合并成一个报文;但挥手时被动方的ACKFIN通常分开发送,因此比握手多一步,形成四次交互。

补充:如果被动方收到FIN时已经没有数据待发送,可以把ACKFIN合并为一个报文,此时会表现为三次挥手,但这是特殊场景,标准流程设计为四次以兼容多数场景。

核心状态详解与资源管理

CLOSE_WAIT(被动关闭方)

·此状态的应用层必须显式调用 close() 才会进入 LAST_ACK,从而发出 FIN。

·常见问题:如果应用逻辑没有正确处理对端关闭事件(即未在 read 返回 0 后关闭连接),连接会长时间停留在 CLOSE_WAIT,造成资源泄漏(fd 未释放)

FIN_WAIT_2(主动关闭方)

·连接已半关闭,本端等待对端发送 FIN。

·风险:如果对端一直不发 FIN,本端会永远停滞在这个状态。为防止这种情况,Linux 有 tcp_fin_timeout 参数(默认 60 秒),如果在指定时间内未收到 FIN,连接会直接强制关闭。

TIME_WAIT(主动关闭方)—— 最精心设计的状态

这是四次挥手中最关键、最容易产生疑问的状态

断开连接的本质是建立双方断开连接的共识。

做法:Client—>Server,Server—>Client。

Client—>Server:我要发的数据已经发完了,我要断开连接!

Server—>Client:我给你发的数据已经发完了,我也要断开连接!

如果Client端已经退出,或者关闭,Server端就是不关闭,close_wait依旧占用文件fd,连接没释放!

fd必须释放,否则会fd泄漏,fd也是一种资源!

主动断开连接的那一方要进入Time_wait状态,即使是4次挥手。

进入Time_wait的话,此时意味着主动断开连接的一方想立即重启绑定历史端口号,IP就不能bind了。因为在主动断开连接的一方,在Time_wait的时候,它的连接还被占用,端口号还在被使用,所以会绑定失败。

为什么要进行Time_wait,就是要确保历史信息的消散。

TCP三大核心机制

确认应答(ACK)机制

理解TCP可靠性的本质:

1.具有应答:可以确保对历史消息的可靠性,报文不一定是最新的,但一定是可靠的!

2.通信中,最新的报文永远没有应答,最新可靠性无法保证!

3.只要服务器给客户端作应答,就能保证由客户端到服务器朝向的可靠性;

4.服务器对客户端发消息,客户端也要作应答。就能保证由服务器到客户端朝向的可靠性。

TCP将每个字节的数据都进⾏了编号.即为序列号.

应答方式

更通用的过程:一次性发多条报文,一次性应答多次。

缺点:哪条报文丢失会不知道,哪条报文没接收也不知道。

因此,有了32位序列号与确认序号(序号值+1),来防止上面缺点情况。比如我发了个报文序号为100,应答时,确认序号为101。

指定报文序号之前的所有信息已经全部收到。也就是说,收到确认序号101,则101之前的所有报文都收到了,下一次发送从确认序号开始。

这种做法的好处是允许少量的报文丢失!

例如:我发了100、200、300、400序号的报文,却只收到了401确认序号,说明401之前的报文都收到了。

接收乱序问题(不可靠问题)

报文按顺序发,不一定按顺序收。

因此,有序号,排序,解决乱序问题;

通信双方传递的全部都是TCP报文,最少也得是一个报头!可以没有数据,但必须有报头!

为什么有两个序号(序列号和确认序号)?

捎带应答:即需要对对方的报文作确认,自己的报文也要有序号!各自一个报头防止重复使用序号。

捎带应答:应答+数据(有效数据载荷)。

超时重传机制

• 主机A发送数据给B之后,可能因为⽹络拥堵等原因,数据⽆法到达主机B;

• 如果主机A在⼀个特定时间间隔内没有收到B发来的确认应答,就会进⾏重发;

但是,主机A未收到B发来的确认应答,也可能是因为ACK丢失了;

因此主机B会收到很多重复数据.那么TCP协议需要能够识别出那些包是重复的包,并且把重复的丢弃 掉.

这时候我们可以利⽤前⾯提到的序列号,就可以很容易做到去重的效果.

那么,如果超时的时间如何确定?

• 最理想的情况下,找到⼀个最⼩的时间,保证"确认应答⼀定能在这个时间内返回".

• 但是这个时间的⻓短,随着⽹络环境的不同,是有差异的.

• 如果超时时间设的太⻓,会影响整体的重传效率;

• 如果超时时间设的太短,有可能会频繁发送重复的包;

TCP为了保证⽆论在任何环境下都能⽐较⾼性能的通信,因此会动态计算这个最⼤超时时间.

• Linux中(BSDUnix和Windows也是如此),超时以500ms为⼀个单位进⾏控制,每次判定超时重发 的超时时间都是500ms的整数倍.

• 如果重发⼀次之后,仍然得不到应答,等待2*500ms后再进⾏重传.

• 如果仍然得不到应答,等待4*500ms进⾏重传.依次类推,以指数形式递增.

• 累计到⼀定的重传次数,TCP认为⽹络或者对端主机出现异常,强制关闭连接.

连接管理机制

在正常情况下,TCP要经过三次握⼿建⽴连接,四次挥⼿断开连接

连接建立:三次握手
同步初始序列号:随机 ISN 防止新旧数据混淆。

协商选项:MSS、窗口缩放、时间戳、SACK 等,只在握手期间完成。

防止旧连接请求导致的错误:三次握手最终确认客户端确实要建连接,避免服务器因滞后的 SYN 报文直接进入 ESTABLISHED 而空等。

连接释放:四次挥手与 TIME_WAIT
释放连接的管理核心是全双工半关闭与TIME_WAIT。

半关闭允许主动关闭方不再发送数据,但仍能接收,直到被动方也关闭。这保证了全双工通道任一方向都不会被提前中断。

TIME_WAIT 是管理最精妙的部分:主动关闭方进入 TIME_WAIT,等待 2MSL。

可靠终止:确保最后发送的 ACK 能被对方收到,如果对方重传 FIN,这边能重发 ACK。

消去网络中残留报文:让旧连接所有滞留的段都因 TTL 耗尽而消失,防止这些“幽灵报文”被下一个相同五元组的新连接误接收。
这是 TCP 为“干净关闭”付出的必要代价。

滑动窗⼝

前面我们讨论了确认应答策略,对每⼀个发送的数据段,都要给⼀个ACK确认应答.收到ACK后再发送下 ⼀个数据段.这样做有⼀个⽐较⼤的缺点,就是性能较差.尤其是数据往返的时间较⻓的时候.

既然这样⼀发⼀收的⽅式性能较低,那么我们⼀次发送多条数据,就可以⼤⼤的提⾼性能(其实是将多个 段的等待时间重叠在⼀起了).

• 窗⼝⼤⼩指的是⽆需等待确认应答⽽可以继续发送数据的最⼤值.上图的窗⼝⼤⼩就是4000个字 节(四个段).

• 发送前四个段的时候,不需要等待任何ACK,直接发送;

• 收到第⼀个ACK后,滑动窗⼝向后移动,继续发送第五个段的数据;依次类推;

• 操作系统内核为了维护这个滑动窗⼝,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只 有确认应答过的数据,才能从缓冲区删掉;

• 窗⼝越⼤,则⽹络的吞吐率就越⾼;

那么如果出现了丢包,如何进⾏重传?这⾥分两种情况讨论.

情况⼀:数据包已经抵达,ACK被丢了.

这种情况下,部分ACK丢了并不要紧,因为可以通过后续的ACK进⾏确认;

情况⼆:数据包就直接丢了.

• 当某⼀段报⽂段丢失之后,发送端会⼀直收到1001这样的ACK,就像是在提醒发送端"我想要的是 1001" ⼀样;

• 如果发送端主机连续三次收到了同样⼀个"1001"这样的应答,就会将对应的数据1001-2000重 新发送;

• 这个时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001-7000)接收端其实之前 就已经收到了,被放到了接收端操作系统内核的接收缓冲区中;

这种机制被称为"⾼速重发控制"(也叫"快重传").

滑动窗口大小、移动问题

一次最大发送数据量由滑动窗口决定。

滑动窗口是发送缓冲区的一部分!

start=确认序号,end=start+win;

滑动窗口大小=end-start;序号在轮次中的数字是依次增大的,也就意味着滑动窗口未来基本是向右滑动的!(宏观)

滑动窗口=对方的接收能力(收到的报文中的Minwin大小(暂时));

滑动窗口的本质:是流量控制的具体实现方案!

窗口移动的本质:start&&end下标增加;

问题:

1.滑动窗口会向左移动吗?

不会!

2.滑动窗口可以变大吗?可以变小吗?可以不变吗?

可以变大,变小,不变,甚至可以为0,完全取决于对方的接收能力;

3.如果报文丢了怎么办?滑动窗口会不会跳过报文进行应答?

不会,确认序号的定义,确认消息一定是连续确认的。

确认消息一定是连续确认的,发送必须连续发送,滑动窗口不能跳跃!

滑动窗口三种报文丢失情况

1.最左侧报文丢失;

2.中间报文丢失;

3.最右侧报文丢失;

这里先引入快重传VS超时重传

快重传和超时重传是同时存在的,超时重传为快重传兜底。

快重传:收到3个同样的ACK(确认序号),就把该序号的下一个报文立即重传;

超时重传:在快重传触发不了的时候作为兜底。

TCP发出,暂时没有应答的时候,必须让对应的报头暂时保存起来,以便后续的重传!

对应报头保存在哪里?如何理解保存?

保存在滑动窗口里,只要滑动窗口没有收到应答,就不会右移(暂时保存)。右移就是删除最左侧数据。

所以超时重传和快重传底层支持是滑动窗口。

回到上面3个情况:

中间报文丢失,滑动窗口还会移动,会变成最左侧报文丢失,最右侧丢失最后也会变成最左侧丢失。

丢失就重传,收到应答后就移动窗口,上面3种情况最后都会演变成一种情况:最左侧丢失。

流量控制

接收端处理数据的速度是有限的.如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送 端继续发送,就会造成丢包,继⽽引起丢包重传等等⼀系列连锁反应.

因此TCP⽀持根据接收端的处理能⼒,来动态调整发送端的发送速度.这个机制就叫做流量控制(Flow Control);

• 接收端将⾃⼰可以接收的缓冲区剩余空间⼤⼩放⼊TCP⾸部中的"窗⼝⼤⼩"字段,通过ACK端通 知发送端;

• 窗⼝⼤⼩字段越⼤,说明⽹络的吞吐量越⾼;

• 接收端⼀旦发现⾃⼰的缓冲区快满了,就会将窗⼝⼤⼩设置成⼀个更⼩的值通知给发送端;

• 发送端接受到这个窗⼝之后,就会减慢⾃⼰的发送速度;

• 如果接收端缓冲区满了,就会将窗⼝置为0;这时发送⽅不再发送数据,但是需要定期发送⼀个窗⼝ 探测数据段,使接收端把窗⼝⼤⼩告诉发送端.

接收端如何把窗⼝⼤⼩告诉发送端呢?回忆我们的TCP⾸部中,有⼀个16位窗⼝字段,就是存放了窗⼝⼤ ⼩信息; 那么问题来了,16位数字最⼤表⽰65535,那么TCP窗⼝最⼤就是65535字节么? 实际上,TCP⾸部40字节选项中还包含了⼀个窗⼝扩⼤因⼦M,实际窗⼝⼤⼩是窗⼝字段的值左移M位;

拥塞控制

虽然TCP有了滑动窗⼝这个⼤杀器,能够⾼效可靠的发送⼤量的数据.但是如果在刚开始阶段就发送⼤量 的数据,仍然可能引发问题.

因为⽹络上有很多的计算机,可能当前的⽹络状态就已经⽐较拥堵.在不清楚当前⽹络状态下,贸然发送 ⼤量的数据,是很有可能引起雪上加霜的.

TCP引⼊慢启动机制,先发少量的数据,探探路,摸清当前的⽹络拥堵状态,再决定按照多⼤的速度传输 数据;

• 此处引⼊⼀个概念称为拥塞窗⼝

• 发送开始的时候,定义拥塞窗⼝⼤⼩为1;

• 每次收到⼀个ACK应答,拥塞窗⼝加1;

• 每次发送数据包的时候,将拥塞窗⼝和接收端主机反馈的窗⼝⼤⼩做⽐较,取较⼩的值作为实际发 送的窗⼝;

像上⾯这样的拥塞窗⼝增⻓速度,是指数级别的."慢启动"只是指初使时慢,但是增⻓速度⾮常快.

• 为了不增⻓的那么快,因此不能使拥塞窗⼝单纯的加倍.

• 此处引⼊⼀个叫做慢启动的阈值

• 当拥塞窗⼝超过这个阈值的时候,不再按照指数⽅式增⻓,⽽是按照线性⽅式增⻓

• 当TCP开始启动的时候,慢启动阈值等于窗⼝最⼤值;

• 在每次超时重发的时候,慢启动阈值会变成原来的⼀半,同时拥塞窗⼝置回1;

少量的丢包,我们仅仅是触发超时重传;⼤量的丢包,我们就认为⽹络拥塞;

当TCP通信开始后,⽹络吞吐量会逐渐上升;随着⽹络发⽣拥堵,吞吐量会⽴刻下降;

拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对⽅,但是⼜要避免给⽹络造成太⼤压⼒的折中⽅案.

TCP拥塞控制这样的过程,就好像热恋的感觉

延迟应答

如果接收数据的主机⽴刻返回ACK应答,这时候返回的窗⼝可能⽐较⼩.

• 假设接收端缓冲区为1M.⼀次收到了500K的数据;如果⽴刻应答,返回的窗⼝就是500K;

• 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;

• 在这种情况下,接收端处理还远没有达到⾃⼰的极限,即使窗⼝再放⼤⼀些,也能处理过来;

• 如果接收端稍微等⼀会再应答,⽐如等待200ms再应答,那么这个时候返回的窗⼝⼤⼩就是1M;

⼀定要记得,窗⼝越⼤,⽹络吞吐量就越⼤,传输效率就越⾼.我们的⽬标是在保证⽹络不拥塞的情况下 尽量提⾼传输效率;

那么所有的包都可以延迟应答么?肯定也不是;

• 数量限制:每隔N个包就应答⼀次;

• 时间限制:超过最⼤延迟时间就应答⼀次;

具体的数量和超时时间,依操作系统不同也有差异;⼀般N取2,超时时间取200ms;

捎带应答

在延迟应答的基础上,我们发现,很多情况下,客⼾端服务器在应⽤层也是"⼀发⼀收"的.意味着客⼾端 给服务器说了"Howareyou",服务器也会给客⼾端回⼀个"Fine,thankyou";

那么这个时候ACK就可以搭顺⻛⻋,和服务器回应的"Fine,thankyou"⼀起回给客⼾端

⾯向字节流

创建⼀个TCP的socket,同时在内核中创建⼀个发送缓冲区和⼀个接收缓冲区;

• 调⽤write时,数据会先写⼊发送缓冲区中;

• 如果发送的字节数太⻓,会被拆分成多个TCP的数据包发出;

• 如果发送的字节数太短,就会先在缓冲区⾥等待,等到缓冲区⻓度差不多了,或者其他合适的时机 发送出去;

• 接收数据的时候,数据也是从⽹卡驱动程序到达内核的接收缓冲区;

• 然后应⽤程序可以调⽤read从接收缓冲区拿数据;

• 另⼀⽅⾯,TCP的⼀个连接,既有发送缓冲区,也有接收缓冲区,那么对于这⼀个连接,既可以读数 据,也可以写数据.这个概念叫做全双⼯

由于缓冲区的存在,TCP程序的读和写不需要⼀⼀匹配,例如:

• 写100个字节数据时,可以调⽤⼀次write写100个字节,也可以调⽤100次write,每次写⼀个字节;

• 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以⼀次read100个字节,也可以 ⼀次read⼀个字节,重复100次;

粘包问题

• ⾸先要明确,粘包问题中的"包",是指的应⽤层的数据包.

• 在TCP的协议头中,没有如同UDP⼀样的"报⽂⻓度"这样的字段,但是有⼀个序号这样的字段.

• 站在传输层的⻆度,TCP是⼀个⼀个报⽂过来的.按照序号排好序放在缓冲区中.

• 站在应⽤层的⻆度,看到的只是⼀串连续的字节数据.

• 那么应⽤程序看到了这么⼀连串的字节数据,就不知道从哪个部分开始到哪个部分,是⼀个完整的 应⽤层数据包.

那么如何避免粘包问题呢?归根结底就是⼀句话,明确两个包之间的边界.

• 对于定⻓的包,保证每次都按固定⼤⼩读取即可;例如上⾯的Request结构,是固定⼤⼩的,那么就 从缓冲区从头开始按sizeof(Request)依次读取即可;

• 对于变⻓的包,可以在包头的位置,约定⼀个包总⻓度的字段,从⽽就知道了包的结束位置;

• 对于变⻓的包,还可以在包和包之间使⽤明确的分隔符(应⽤层协议,是程序猿⾃⼰来定的,只要保 证分隔符不和正⽂冲突即可);

对于UDP协议来说,是否也存在"粘包问题"呢?

• 对于UDP,如果还没有上层交付数据,UDP的报⽂⻓度仍然在.同时,UDP是⼀个⼀个把数据交付给 应⽤层.就有很明确的数据边界.

• 站在应⽤层的站在应⽤层的⻆度,使⽤UDP的时候,要么收到完整的UDP报⽂,要么不收.不会出 现"半个"的情况.

TCP异常情况

进程终⽌:进程终⽌会释放⽂件描述符,仍然可以发送FIN.和正常关闭没有什么区别.

机器重启:和进程终⽌的情况相同.

机器掉电/⽹线断开:接收端认为连接还在,⼀旦接收端有写⼊操作,接收端发现连接已经不在了,就会进 ⾏reset. 即使没有写⼊操作,TCP⾃⼰也内置了⼀个保活定时器,会定期询问对⽅是否还在.如果对⽅不 在,也会把连接释放.

另外,应⽤层的某些协议,也有⼀些这样的检测机制.例如HTTP⻓连接中,也会定期检测对⽅的状态.例 如QQ,在QQ断线之后,也会定期尝试重新连接.

TCP⼩结

为什么TCP这么复杂?因为要保证可靠性,同时⼜尽可能的提⾼性能.

可靠性: • 校验和 • 序列号(按序到达) • 确认应答 • 超时重发 • 连接管理 • 流量控制 • 拥塞控制

提⾼性能: • 滑动窗⼝ • 快速重传 • 延迟应答 • 捎带应答

其他: • 定 时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)

基于TCP应⽤层协议

• HTTP • HTTPS• SSH • Telnet • FTP • SMTP

当然,也包括你⾃⼰写TCP程序时⾃定义的应⽤层协议;

TCP/UDP对⽐

我们说了TCP是可靠连接,那么是不是TCP⼀定就优于UDP呢?TCP和UDP之间的优点和缺点,不能简单, 绝对的进⾏⽐较

• TCP⽤于可靠传输的情况,应⽤于⽂件传输,重要状态更新等场景;

• UDP⽤于对⾼速传输和实时性要求较⾼的通信领域,例如,早期的QQ,视频传输等.另外UDP可以 ⽤于⼴播;

归根结底,TCP和UDP都是程序员的⼯具,什么时机⽤,具体怎么⽤,还是要根据具体的需求场景去判定.

对比维度 TCP UDP
连接特性 面向连接,通信前需三次握手建立连接,结束后四次挥手释放 无连接,无需建立和释放连接,直接发送数据
可靠性 可靠交付,保证数据无丢失、无重复、按序到达,支持确认应答与超时重传 不可靠交付,不保证送达、不保证顺序、丢包不重传
传输形式 面向字节流,数据拆分为字节流传输,无报文边界 面向报文,对应用层报文直接封装,完整保留报文边界
首部开销 固定 20 字节首部,可扩展选项,整体开销较大 固定 8 字节首部,开销极小
传输效率 速度较慢,受确认、重传、拥塞控制影响,延迟高且波动大 速度极快,无额外控制开销,延迟低且稳定
控制机制 具备流量控制、拥塞控制,可动态适配网络与接收端状态 无流量控制、拥塞控制,始终按应用速率发送
通信模式 仅支持一对一通信 支持一对一、一对多(广播 / 多播)、多对多
适用场景 对可靠性要求高的场景:网页浏览、文件传输、邮件、远程登录 对实时性要求高的场景:直播、语音通话、在线游戏、视频会议
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐