详解TCP/UDP网络协议

​​​​​​​

目录

TCP 和 UDP 有哪些区别?

UDP介绍

UDP的特点

UDP应用场景

TCP介绍

TCP头部结构

TCP特点

TCP 的三次握手

TCP 四次挥手

流量控制

拥塞控制


​​​​​​​TCP和UDP是传输层最常用的俩大协议,在我们开发的过程中,我们该如何抉择,他们俩兄弟又有什么不同呢?​​​​​​​

TCP 和 UDP 有哪些区别?

​​​​​​​大部门人应该都知道TCP是面向连接的,而UDP是面向无连接的。那么什么是面向连接?难道真的是客户端与服务器之间建立了一条秘密通道?又或是真的用什么东西将服务器与客户端连接起来了?并不是,所谓的连接,仅仅只是在服务器与客户端俩端制定了一些数据结构来维护双方的交互状态,这些数据结构可以保证所谓面向连接的特性

比如,TCP传输数据是可靠的。有序、无差错,保证不丢包,不重复。而UDP跟IP包一样,不保证不丢失,也不保证数据顺序到达,换句话说,UDP传输包就像小孩出门一样,有可能走着走着半路就迷路了,或者是被坏人用糖果骗走了丢了,而TCP就像个成年人,自律性跟防范心都很强。

比如,TCP传输是面向字节流的。TCP会以流的形式从应用层读取数据并存放在自己的发送缓存区中,同时为这些字节标上序号;TCP会从发送方缓冲区选择适量的字节组成TCP报文,通过网络层发送给目标;目标会读取字节并存放在自己的接收方缓冲区中,并在合适的时候交付给应用层。正因为是字节流,没头没尾的, 所以有个缺点就是在处理的时候需要粘包拆包。而 UDP 继承了 IP 的特性,基于数据报的,一个一个地发,一个一个地收。

比如,TCP是可以控制传输速度的,即拥塞控制。它能够感受到网络环境好坏,从而调整自己的传输速度。UDP就有点愣头青了,管他三七二十一,让我发我就发。

UDP介绍

UDP包结构

可以看到UDP包结构很简单,跟TCP对标简直简单的一塌糊涂。包头就俩个端口,源端口跟目标端口,无论应用程序写的使用 TCP 传数据,还是 UDP 传数据,都要监听一个端口。正是这个端口,用来区分应用程序,要不说端口不能冲突呢。两个应用监听一个端口,到时候包给谁?

UDP的特点

1.结构简单。没有TCP那些花花肠子(大量的数据结构、处理逻辑、包头字段),正因为UDP简单,所以有很高的扩展性,往往使用UDP的领域都是自己维护了些数据结构来扩展一些机制弥补UDP的一些缺点。

2.轻信他人。它不会建立连接,虽然有端口号,但是监听在这个地方,谁都可以传给他数据,他也可以传给任何人数据,甚至可以同时传给多个人数据。

3.愣头青,做事不懂权变。不知道什么时候该坚持,什么时候该退让。它不会根据网络的情况进行发包的拥塞控制,无论网络丢包丢成啥样了,它该怎么发还怎么发。

UDP应用场景

1.QUIC(全称 Quick UDP Internet Connections,快速 UDP 互联网连接),Google提出的一种介于UDP的通信协议,http3.0也应用上了,目的就是降低网络延迟,提升用户体验。

2.实时性游戏。比如FPS、MOBA这类型的游戏,实时性要求都很高,像英雄联盟在对战过程中,每个人的操作、走位都有很强的实时性,如果用TCP,丢包重传,堵塞后面的数据传输,你卡了一下等你缓过来游戏都可能已经黑屏了。

TCP介绍

TCP头部结构

首先,源端口号和目标端口号是不可少的,这一点和 UDP 是一样的。如果没有这两个端口号。数据就不知道应该发给哪个应用。

接下来是包的序号。为什么要给包编号呢?当然是为了解决乱序的问题。不编好号怎么确认哪个应该先来,哪个应该后到呢。

再接下来就是确认序号,发出去的包对方收到了需要给我确认某个序号的包收到了,如果没有收到确认消息,那么久要重新发送,直到对方确认收到。这个机制就是为了解决不丢包的问题。因为TCP是靠谱的协议。我不能保证网络层不丢包,所以我TCP只能通过不断重传,至少在我传输层会努力保证可靠性。

紧接着是首部长度,也叫数据偏移,它指TCP报文段的数据起始处距离TCP报文段的起始处有多远。这个字段实际上是指出TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的。

接下来有一些状态位。例如 SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。

还有一个重要的就是窗口大小。TCP 要做流量控制,通信双方各声明一个窗口,标识自己当前能够的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。

然后就是效验和,效验和是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。防止包被串改。

紧急指针。当值为1时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据),此时会打破流量控制的限制。

选项字段最最重要的就是MSS字段(又称为最大段大小)。连接的每一个端点一般都在它的第一个SYN报文上指定MSS的值,用来标明自己希望接收的最大段的消息。

TCP特点

顺序问题 ,稳重不乱;

丢包问题,承诺靠谱;

连接维护,有始有终;

流量控制,把握分寸;

拥塞控制,知进知退。

TCP 的三次握手

TCP 的连接建立,我们常常称为三次握手。三次握手是什么?为什么要三次握手,俩次不行吗?打个比方,张三跟李四开黑玩游戏,然后打开语音,张三说:喂,听得到吗,假如过了会张三还没听到李四的响应,那么张三会觉得是不是自己麦有问题导致李四没听到自己说的话,或者说李四的麦有问题语音传达不过来,又或者是李四的耳机有问题,听不到我说的话,假如张三听到了李四的响应,李四说:听得到听得到,这时候证明张三的麦没问题,李四的麦跟耳机都没问题,这时候就轮到李四等待张三的回应了,假如没听到张三的回应,那么李四会想张三是不是没听到,是不是我的麦有问题,或者张三的耳机有问题,假如听到了张三说:好的,我也能听到。这时候就代表双方的麦跟耳机都没问题了,那么就可以愉快的开黑了。

三次握手除了双方建立连接外,还沟通一件事情,就是 TCP 包的序号的问题。A 要告诉 B,我这面发起的包的序号起始是从哪个号开始的,B 同样也要告诉 A,B 发起的包的序号起始是从哪个号开始的,为什么序号不能都从 1 开始呢?因为这样往往会出现冲突。例如,A 连上 B 之后,发送了 1、2、3 三个包,但是发送 3 的时候,中间丢了,或者绕路了,于是重新发送,后来 A 掉线了,重新连上 B 后,序号又从 1 开始,然后发送 2,但是压根没想发送 3,但是上次绕路的那个 3 又回来了,发给了 B,B 自然认为,这就是下一个包,于是发生了错误。因而,每个连接都要有不同的序号。这个序号的起始序号是随着时间变化的,可以看成一个 32 位的计数器,每 4 微秒加一,如果计算一下,如果到重复,需要 4 个多小时,那个绕路的包早就死翘翘了,因为我们都知道 IP 包头里面有个 TTL,也即生存时间。

连接建立完了,我们来看下双方状态变化的时序图:

 一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。然后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。服务端收到发起的连接,返回 SYN,并且 ACK 客户端的 SYN,之后处于 SYN-RCVD 状态。客户端收到服务端发送的 SYN 和 ACK 之后,发送 ACK 的 ACK,之后处于 ESTABLISHED 状态,因为它一发一收成功了。服务端收到 ACK 的 ACK 之后,处于 ESTABLISHED 状态,因为它也一发一收了。

看图每次确认序列号ack=序列号seq+1,其实并不一定是+1,而是和发送和接收的数据有关。如果没有发送数据序列号是要加一的,但如果发送了数据则下一次发送的序列号就不是加一了而是加上发送的数据量,而如果没有接收到数据则确认号是要加一的,但如果收到了数据则下一次发送的确认号就不是加一而是加上收到的数据量。

TCP 四次挥手

时序图

 

  1. 机器A发送完数据之后,向机器B请求断开连接,自身进入FIN_WAIT_1状态,表示数据发送完成且已经发送FIN包(FIN标志位为1);

  2. 机器B收到FIN包之后,回复ack包表示已经收到,但此时机器B可能还有数据没发送完成,自身进入CLOSE_WAIT状态,表示对方已发送完成且请求关闭连接,自身发送完成之后可以关闭连接;

  3. 机器B数据发送完成之后,发送FIN包给机器B ,自身进入LAST_ACK状态,表示等待一个ACK包即可关闭连接;

  4. 机器A收到FIN包之后,知道机器B也发送完成了,回复一个ACK包,并进入TIME_WAIT状态

    TIME_WAIT状态比较特殊。当机器A收到机器B的FIN包时,理想状态下,确实是可以直接关闭连接了;但是:

    1. 我们知道网络是不稳定的,可能机器B 发送了一些数据还没到达(比FIN包慢);
    2. 同时回复的ACK包可能丢失了,机器B会重传FIN包;

    如果此时机器A马上关闭连接,会导致数据不完整、机器B无法释放连接等问题。所以此时机器A需要等待2个报文生存最大时长,确保网络中没有任何遗留报文了,再关闭连接

  5. 最后,机器A等待两个报文存活最大时长之后,机器B 接收到ACK报文之后,均关闭连接,进入CLASED状态

双方之间4次互相发送报文来断开连接的过程,就是四次挥手

流量控制

接收方可以根据自己的处理速度告诉发送方一个窗口大小,让发送方根据这个窗口的值控制自己的返送速度,一句话描述就是你别发的太快把我撑死了,也别发的太慢把我饿死了。

为了保证顺序性,每一个包都有一个 ID。在建立连接的时候,会商定起始的 ID 是什么,然后按照 ID 一个个发送。为了保证不丢包,对于发送的包都要进行应答,但是这个应答也不是一个一个来的,而是会应答某个 ID,表示之前的都收到了,这种模式称为累计确认或者累计应答(cumulative acknowledgment)。

为了实现这个模式,TCP引入了滑动窗口的概念。

发送端数据结构:


接收端数据结构

未发送可发送部分就是接收方告诉发送方我还能接收这么多,无需等待之前ID的确认,可以一次性将未发送可发送部分的数据传输完。窗口的实现实际上是在操作系统开辟一个缓存空间(空间和序号都是有限的,并且要循环使用,一般为环形队列),发送主机在等到确认应答返回之前,必须在缓冲区保留已发送未确认的数据(超时重传),收到应答后,才能将此数据清除。如果接收方实在处理的太慢,导致缓存中没有空间了,可以通过确认信息修改窗口的大小,甚至可以设置为 0,则发送方将暂时停止发送。

拥塞控制

拥赛控制也是通过窗口的大小来控制的,前面的滑动窗口 (rwnd) 是怕发送方把接收方缓存塞满,而拥塞窗口 (cwnd),是怕发送方把网络塞满。对于TCP来说,我在传输层,他压根不知道整个网络路径都会经历什么,对他来讲就是一个黑盒。拥塞控制很像生活中用漏斗往瓶子里灌水的行为。假如你一开始就往漏斗里面灌水过快,那么水肯定就会溢出漏斗,你发包发的太快了,那么很多包必然就丢失了,灌的太慢了,有点费时间,手也酸呐是不是,所以我们通常会开始慢慢来,然后根据情况控制自己灌水的速度。这叫慢启动,TCP发包也是一样的,先慢慢发,然后根据接收方确认的情况来判断要不要加速,如果包的确认速度没什么问题,就加大传输速度,如果发现有延迟或者压根没收到确认则降低传输速度,也不是一开始没收到确认就降低速度,因为公网上带宽不满也会丢包,这个时候就认为拥塞了,这是不对的。但是你也不能等大量丢包再去降低传输速度,这时候就晚了。为了解决这个问题聪明的TCP制定了TCP BBR 拥塞算法,它企图找到一个平衡点,就是通过不断地加快发送速度,将管道填满,但是不要填满中间设备的缓存,因为这样时延会增加,在这个平衡点可以很好的达到高带宽和低时延的平衡。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

·码上修·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值