avatar

计算机网络-传输层

计算机网络-传输层

1. 概述

用户进行网络编程的时候一般是使用到传输层所提供的接口,所以传输层一般也被称为用户的最底层,同时也是面向通信的最高层。传输层的主要任务是管理端到端的通信连接。

PC上的浏览器通过网络层穿过复杂的互联网找到某个网站的后台,后台与浏览器进行通信也可以看做是一种进程间的通信,而传输层就是解决这两个进程间是如何通信的。那如何识别到底是PC上哪个浏览器进程在找网站的后台(可看做另一个进程)要数据的呢?实际上是引入端口(Port,0~65535)来标记不同的网络进程。下表就展示了几个常见端口

FTP HTTP HTTPS DNS TELNET
21 80 443 53 23

传输层的两大协议就是TCP协议UDP协议,后面会分别介绍。

2. UDP协议

UDP(用户数据报协议)协议,是一个非常简单的协议,在获得应用层的数据报后不会做任何的处理,所以UDP的协议下的长度是由应用层给的协议的长度所决定的。

UDP协议的特点:

  • 无连接协议
  • 不能保证可靠的交付数据(无法感知是否丢失)
  • 面向报文传输(不会对报文做任何处理)
  • 没有拥塞控制
  • 首部开销小(只有8个字节,包括源端口、目的端口、长度、检验和)

3. TCP协议

3.1 TCP协议概述

TCP(传输控制协议)协议,相较于UDP协议就复杂需多。TCP协议存在于IP数据包中,包含TCP首部TCP数据报的数据两部分。

TCP数据报.PNG

TCP协议的特点:

  • 面向连接协议
  • 一个连接有两端(点对点通信)
  • 提供可靠的传输服务
  • 提供全双工通信
  • 面向字节流协议(对用户数据进行适当的合并或者拆分发送)

3.2 TCP协议详解

TCP协议的头部包含的信息和其可靠性是息息相关的。所以需要对其中的部分信息进行详述。

TCP首部信息.PNG

这些信息是属于TCP首部的,源端口和目的端口分别是16位(总共4个字节),首部中的前20个字节是固定的。

  1. 数据偏移

先介绍数据偏移,数据偏移顾名思义就是数据区偏移首部的距离。因为TCP首部中有包含选项,所以首部的长度不定,TCP首部一般在20~40个字节。

  1. TCP标记
标记 含义
URG Urgent: 紧急位,URG=1,表示紧急数据
ACK Acknowledgement: 确认位,ACK=1,确认号才生效
PSH Push: 推送位,PSH=1,尽快地把数据交付给应用层
RST Reset: 重置
SYN Synchronization: 同步位,SYN=1 表示连接请求报文
FIN Finish: 终止位,FIN=1 表示释放连接

每一位都有不同的意义,后面在叙述序列号时还会提及。

  1. 窗口

窗口指明允许对方发送的数据量,比如确认号是500,窗口大小是1000,那么500~1500都是可以成功接收的。

  1. 校验和

就是用来检验的。

  1. 紧急指针

URG置1后,紧急指针指出紧急数据在本段报文的位置。

  1. 序列号(Sequence Number,SEQ

序列号是很重要的,为了保证TCP的可靠性,TCP的Client和Server端都要准确的获取对方的序列号。

TCP传输协议给每一个字节都赋予序列号,如果没有同步化旗标(SYN)TCP头部中的Seq对应的是TCP数据区第一个字节数据的序列号。如果有的话,则代表这是最初的序列号,第一个字节数据的序列码为当前序列号加1。

1
2
3
4
序列号并不是这个区段数据的ID或者编号!
它不代表唯一的区段。

Wireshark抓包时,抓的是相对的Seq,并不是绝对的Seq,因为真正的值可能很大,不方便看。
  1. 确认号(Ack)

表示已成功接收序列号为ack-1前的数据,期望接收下一个字节的序列号为ack。所以ack本质上是期望收到的数据的开始序列号。

3.3 TCP可靠性传输原理

停止等待协议

具体实现的方法就是每发送一个消息,就设置一个定时器,超过一定时间就重新传输,但是如果一个一个的传输对信道的利用率太低。

连续ARQ协议

建立一个滑动窗口,用来暂时缓存字节流。发送方和接收方都有一个窗口。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定的距离。接收窗口的滑动类似,通过判断自己的发送确认是否被发送方收到来移动自己的窗口。

3.3 TCP流量控制

流量控制就是通过控制滑动窗口以不让发送方发送速率太快,保证接收方来得及接收。

TCP流量控制.PNG

图中rwnb就代表当前接收方可以接收1000个数据。

如果最后一条丢失了,也就是发送方收到的最新窗口为0,发送方就会停止发送,这里就需要引入一个新的定时器-坚持定时器。坚持定时器的作用就是当发送方接收到窗口为0时,就启动这个定时器,并每隔一段时间发送一个窗口探测报文,以询问接收方当前窗口有多大。

3.4 TCP拥塞控制

网络中出现拥塞的话,分组将会丢失,发送方就会重传,从而拥塞加剧,所以出现拥塞时应该控制发送方的速率。
可以发现拥塞控制的表现和流量控制很像,但是出发点是不同的。

  • 流量控制是为了让接收方来得及接收,也就是只考虑点对点的通信量控制。
  • 拥塞控制为了降低整个网络的拥塞程度,考虑整个网络,是全局性的考虑。

可以采用慢启动和拥塞控制算法以避免,需要引入一个状态变量-拥塞窗口(cwnd)。需要注意的是,拥塞窗口不同于发送方窗口,发送窗口才是实际决定发送方能发送多少数据的。

慢启动与拥塞避免

开始以很小的速度(如:只发送一个报文段),当收到确认后,cwnd加倍,以指数形式增长。增加到慢开始门限ssthresh时(cwnd>=ssthresh)进入拥塞避免,每个轮次只将cwnd加1。如果出现超时,ssthresh = cwnd / 2,重新进入慢开始。
拥塞控制下的传输速率变化:

启动拥塞避免算法.PNG

4. TCP连接的建立与释放

本章是面试经常考到的内容,所以放在单独一个小结。因为在关闭连接的时候是不分Cient和Server的,只分谁先主动关,下文就将主动提出请求的一方(也包括建立连接)叫做发送方,另一方叫做接收方。发送方统一在图中的左侧,接收方再图中的右侧。

4.1 TCP连接的建立-三次握手

建立以及释放都用到了前面提到过的TCP标记,TCP三次握手的流程图如下所示:

三次握手.PNG

  • 发送方将SYN=1ack=0,并随机生成序列号seq=x,发给接收方,自己进入同步已发送状态。
  • 接收方收到后,将SYN=1ACK=1,再自行随机生成序列号seq=y,并将确认号ack置为发送方的序列号加1(x+1),发送回去,进入同步已接收状态。
  • 发送方收到后,将ACK=1,将确认号ack=y+1),将序列号seq=x+1,发送给接收方并进入建立连接状态。
  • 接收方接收到并进入建立连接状态。
  • 互相进行数据传输。

图中很好的阐述了三次握手的流程,但是需要注意的是前后生成了两个序列号,是发送方、接收方各自生成的,两端进入建立连接状态(ESTABLISHED)的时间是不同的。双方在建立连接的过程中最重要的就是获取对方的序列号。

这里经常询问的是,为什么不可以两次握手建立连接?

两次握手.PNG

发送方第一次发送建立连接请求,但是迟迟没有被接收方收到,于是发送第二次请求,收到接收方回应并建立连接。建立连接后,第一次请求被接收方收到,接收方再开一个连接,但是这个连接双方并没有数据的传输,空耗接收方的资源。但是三次握手会抛弃第一次建立连接请求,所以不能是两次握手。

4.2 TCP连接的释放-四次挥手

四次挥手流程如下:

四次挥手.PNG

建立连接之后ACK=1会一直保持。

  • 发送方发送释放报文,将FIN=1,随机生成seq=u
  • 接收方随机生成seq=w,确认号ack=u+1,发送报文,并进入关闭等待状态(CLOSE_WAIT)。此时TCP已经属于半关闭状态,接收方只能给发送方发送数据,但是发送方不能给接收方发送数据。
  • 当接收方不再需要连接时,再次发送释放报文,将FIN=1,进入最后确认状态(LAST_ACK)。
  • 接收方收到释放报文后,进行确认,将seq=u+1ack=w+1,并进入TIME_WAIT状态(等待2MSL)。
  • 接收方收到发送方的确认后,释放连接。

以上就是四次握手的流程。其中要注意以下几点:

  • 接收端在第一次回信后进入CLOSE_WAIT状态,保留这个状态是为了让接收端发送还未传送完成的数据,当传送彻底完成后,再次发送FIN=1的连接释放报文。这个状态允许持续很长时间,但是如果持续太长时间就可能意味着出现bug了。
  • 发送端彻底发送完成后,不会立刻进入CLOSE状态,而是先进入TIME_WAIT状态,这个状态下是不会释放端口的。一般建议持续的时间是2MSLMSL建议设置2分钟。MSL(Max Segment Lifetime),即最长报文段寿命,是网络报文在网络中可以存活的最长时间。目的是
    • 确保最后一个报文能到达。如果接收方没有收到最后条报文,接收方就会重新发连接释放请求报文,发送方也就知道接收方没有收到最后一个报文了,也就会重新发送最后一条报文,等待一段时间的目的就是为了处理这种情况。
    • 确保当前连接的所有报文都已经过期。等待一段时间是为了让本连接持续时间内产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

为什么TCP在建立连接的时候不能四次握手,而只有三次握手呢?

在前面的三次握手也好,四次握手也好,可以发现除了标记位的变化,最重要的就是发送方与接收方双方核对序列号。但是在关连接的时候,接收方发了两次序列号,中间进入CLOSE_WAIT状态,也叫半打开状态,在这个状态中接收方将最后的数据发送完毕,Linux等操作系统是允许这个状态持续很久的。但是在建立连接的过程中,这个状态是不被允许的,所以接收方在建立连接的时候将序列号必须一次发送完全。

CLOSE_WAIT产生的原因?

CLOSE_WAIT状态就是在关闭的过程中处于半打开状态的,虽然Linux等操作系统允许这个状态持续很长时间,但是一般没有人会这么做,其他的状态都是需要精确的确定时间的,所以如果看到了这个状态的出现,很有可能是出现bug了。

Author: TheOutsider
Link: http://yoursite.com/2020/04/10/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C-%E4%BC%A0%E8%BE%93%E5%B1%82/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.