计算机网络
本文参考至b站方老师课程
文章目录
前言
在前面已经说过了三层; 是否应该在前三层实现可靠传输?可靠传输要求这条线路上只有两台主机A和B,否则错误信息无法正常交付; 若网络层采用这样的结构,则路由器只能提供给通信双方使用,对资源是种巨大浪费,且实现起来较为困难;
可靠传输交于**建立一对一通信线路**的传输层TCP来实现;
传输层向上层屏蔽下层网络中复杂的路由控制,线路选择等操作,向上层提供一个虚拟的通信线路。
一、传输层的端到端通信
1. 端口号
一台主机上应用程序很多,一个应用可能也包含多个进程。我们说的通信必须是同样的进程之间的通信,即端到端的通信。来标识这些应用进程,引入了端口号的概念。
- 端口号有16位,:
- 0~1023用于某些固定的协议和服务使用;
- 1024~49151为登记端口,用于某些用户量的应用程序去专门的组织登记之后,成为该应用接近专用的端口号。
- 49152~65535是用户进程临时使用的端口号。
- 端口号只具有本地意义(相对而言IP具有全球意义),对于不同主机端口号是可以重复的。
2.端口复用和分用
由于实现面向连接太过复杂,没有必要将面向连接再来逐一分类,因此传输层一刀切地将传输层协议分为两大类:不面向连接的UDP协议与面向连接的TCP协议。【复用的根本原因】
在应用程序中,不管什么进程,要么使用TCP,要么使用UDP,不可能再选择其他的协议,这样不同的进程使用同一种协议实现不一定完全相似的功能,叫做复用。
当传输层传递给网络层时,不管是TCP还是UDP,都使用同样的IP协议,这就是IP协议的复用。
相对的,到达接收方之后,需要按照协议、进程端口来分发数据,这就是分用。
3. Udp 报文与Tcp报文段
- UDP会将上层发过来的message完整地封装成一个数据报,不会切片;
注意:UDP不切片,不是说就能传递无限制大小,而是会在网络层切片。这样做会降低网络层性能,因此UDP切片由应用程序进程负责;
- TCP会将报文切成不同字节数的小片,而每次切片的大小会根据各种因素决定。
二、UDP
用户数据报协议(User Datagram Protocol)
1.特点
- 不面向连接;
- 提供最大努力交付(即不可靠交付,但提供校验);
- 不分片;
- 快速、高效;
- 可以提供一对一、一对多、多对多多种通信方式,比较灵活。
- 不必确认对方有没有接收成功。
UDP就好像写信,只要投出去了,之后发生什么就不关我事了。
注意:对方回信不是说对方确认或者建立了连接,而是对方也向你发送了UDP数据。
2. UDP首部
UDP首部非常短,只有8个字节:源端口,目的端口,长度,校验和各占两个字节。
值的一缇的是伪首部:
- 当计算校验和时,若只使用当前UDP首部的内容,可能出现混乱:
由于端口号的本地意义,任何主机都有可能拥有UDP首部上的那组源、目的端口,其他内容也有可能相等,因此这样就分不清谁发给谁了。
为了解决这个问题,计算校验和时会临时生成一个带有源。目的ip的伪首部,这样计算出来的校验和就是独一无二的了。
三、TCP
TCP(传输控制协议,Transimision Control Protocol),是网络传输实现可靠传输的协议,也是一个很复杂的协议。
1. 特点
- 建立连接。每次通信之前,双方会先建立连接;
- TCP只提供一对一的通信方式;
- 全双工通信;
- 面向流;
- 可靠传输;
面向流:
TCP会将上层传下来的数据当做字节流,按照直接顺序标号,放入传输缓存中。
传输过程中会按照不同字节数打包成数据段,并按照网络状况。对方窗口大小等因素调整段中字节数量。
对方收到数据段之后也不会马上读取,而是按照其标号组装,并确认没有丢失后再进行拼接成完整数据流。
TCP的数据块大小具有不确定性,但是数据流大小是确定的。
TCP更像是打电话,只有先确定对方听得到,才会继续说正事。
打电话的时候只能建立一对一连接,且双方随时都能向对方发送信息(全双工)。
2 . Tcp数据传送过程
TCP连接端点:套接字。
TCP的连接是建立在双方的套接字之上的,套接字(socket) = IP地址 : 端口号;
这样方式确定了双方唯一的连接。
因为不可能达到这样。于是想了一个办法:
发送方每发送一条数据段,都会等待对方的确认信息,对方只有发送会确认ACK,我在规定时长中收到确认,就不会重传,否则就会重传。
2.1 各种传输的情况
B在规定时长中确认,A可以继续发下一个。
数据包在半路被路由器丢弃或者对方出错(来不及处理)丢弃,A未收到确认,重传
确认发生丢失或迟到。
A重传,AB双方都会丢弃重复的包【朕已阅】
以上这种,A可以自动重新发送数据,而不用B去催促的方式,叫做自动重传请求(ARQ)。
这样做虽然不会丢包,但是效率会非常慢,大半时都在等待数据或者确认。
采用流水线的方式,一次传送多条数据接收多条确认。若有缺失,从缺失地方重新发送对方丢弃重复的包。
2.2 滑动窗口协议
前面已经提到,TCP会为发送缓存中的每个字节编号,其中一个字节一个序号。
为了根据对方的接收能力调控发送速度,TCP引入了滑动窗口。
当发送的数据报文段被对方接收到以后,对方会发送确认报文,其中会夹带接收方的接收窗口大小。
发送方会根据对方的接收窗口大小动态跳转自己发送窗口的大小。
这是TCP实现流量控制的最主要的一种方法;《具体机制会在下面的TCP报文后详细叙述。》
滑动窗口是发送和接收缓存中的一组连续的空间。《连续针对的是数据中的字节编号。》
滑动窗口会框住从对方未发送确认消息的第一条报文开始总共己方窗口可以容纳的字节量的数据。
滑动窗口根据发送和接收方分为发送窗口和接收窗口。
可靠通信的具体实现:
发送方和接收方的各两个窗口。
窗口示意图在下下张图片。
ps:
缓存、窗口、报文段的概念区别:
缓存是上层传下来的总的数据报,TCP会逐渐将这些数据统统发送出去;
报文段:由于上层传下来的报文可能很长,TCP由于需要好考虑流量控制已经拥塞控制等因素不能将所有数据报一次性发出去,会将报文切片,每次发送一个段;
注意:一段之类包含一组连续序号的字节,但不是说包含一个窗口的字节,窗口发送字节的数量每次发送都不确定,但肯定小于等于窗口容量
窗口:一个抽象的工作空间,会选中缓存中一组连续的字节, 未收到确认之前,即使已经发送出去了的字节也不会从窗口回退,因此一旦窗口已经被未确认的字节占满,窗口就不能再发送数据段了。
2.3 累计确认
TCP接收方的确认报文,为了提升了网络的利用率,会选择在收到几条报文段之后,一次性对之前成功接收的N条报文进行确认,称为累计确认。
伴随着累计确认,会带来不确定哪些报文丢失的问题。 TCP对于这个问题仍然采用一刀切的方法; 接收方会对最近收到的**连续序号**的最后一条报文进行确认,将这个序号的**后一位**发送给发送方。
发送方若在最大等待时间之内,仍然没有收到他发出的最后一个序号的数据的确认报文,他会自上次接收到的确认报文开始,对之后的所有内容进行重传,叫做回退N。
2.4 TCP报文格式
TCP报文最短有20字节,通常也都是20字节。
而有时候为了实现额外的功能,可以对报文长度进行补充,补充信息必须是4字节的整数倍(下图中一行大小为4字节32bit,即多添加n行数据),若补充的数据不足4n,会填充一些无用数据。
因此TCP报文长度为:20 + 4n
- 源/目的端口:发送双方的进程的端口号;
- 序号:本次发送的报文段中包含的字节序号第一个的序号;
- 确认号,ack:接收方希望下次收到的第一个字节序号。(注意理解:ACK = 101若表示之前100个字节已经全部收到。)【但是对方可能已经发送了两百个字节,只确认一百个字节,对方会在超时后从101开始重传,并附加新的内容】
注: 这两个指的必然不属于同一个数据报文段的数据内容;
- 数据偏移:正文数据离报文首部的长度;
- 保留:做其他用途;
- URG:1表示这段报文有紧急的数据,配合紧急指针使用;
- ACK:1表示确认字段ack有效【两个“ack”是不同的】,当首次发送时,必然不存在确认,因此ACK置为0;
- PSH:1表示对方一旦收到这条报文段,马上传给应用进程;
- RST:1表示当前来了解出了一些无可挽回的故障,必须重新建立连接
- SYN:为1表示双方正在建立连接中;
- FIN:1表示当前报文段是最后一条发送的报文。
- 窗口:我方当前的窗口大小;
通常情况下只有一方发送,一方接收;
但必须考虑双方全双工通信的情况,因此发送数据段时必须带上己方的接收窗口大小;
- 校验和:和UDP一样,会带上源/目的ip再计算校验和;
- 紧急指针,当URG=1,此段有效,指向数据部分中紧急数据部分;
- 选项:建立连接时约定双方通信的一些额外细节。
如:
之前我们可能会觉得重传N的机制太蠢了,凭啥中间只丢了一个字节就要重传之后全部的啊?!
这还是为了简化传输层的复杂度。
如果不想这样,可以在选项中设定为选择重传,即接收方会告知对方自己接收到某条之前哪些条没收到,对方只会重传这一条。一般不会这样做,因为接收方会因此要多发很多的确认报文,对单向传输的时候很不利。
2.5 窗口联动实现流量控制
发送方的缓存区分为四个部分:【我方是发送方,对方是接收方】
(1)我方已发送,对方已确认(隔离出窗口,马上从缓存释放<绿色>);
(2)我方已发送,对方未确认(占据发送窗口)
之所以不释放是怕需要重传结果内存中无字节,肯定不能让上层重传给TCP。
因此要继续占据窗口,随时准备重传。
(3)我方未发送,但在窗口中(我方可以任意时刻都能发送《粉红色》);
(4)我方未发送,在缓存排队(等待进入窗口《黄色》);
窗口变动机制:
步骤:
- 我方从窗口选择发送一些字节(如10,即31-40这十条);
- 我方窗口变为10等待重传,10可发送;
- 我方继续发送十条(41-50),我方窗口占满等待;
- 对方发送确认(51),并告知对方接收窗口大小为10;
一次确认多个字节的操作称为捎带确认;
捎带确认需要估摸好时间,在对方等待超时之前必须确认,最好也不要让对方等太长,占用对方资源。
对此,发送方也应该配合好设计合理的超时时间,不能让对方白白等待你重传丢的包,也不能在确认包还在路上的情况下就重传;
等待时间的设计大部分考虑到之前几次传输的等待时间(加权平均,之前占7/8,当前视为偶然,只占1/8)
- 我方缩减窗口大小,框住51-60十条数据发送;
- 等待重传;
- 对方确认61,并再次缩减窗口为0;
- 我方不在发送;
此时,出现了一个问题,对方可能处理完了缓存,并扩大了窗口,等待我方继续发送;
但是由于对方不能在发送确认,我也不知道对方的窗口扩大了,因此双方陷入了死锁。
处理死锁的方式是:
发送方设置一个持续计时器,在超时后发送大小仅为1字节的零窗口探测报文段,对方收到报文,会发送确认包,并附带这次的窗口大小,我方继续发送。
2.6 拥塞控制
当网络状况不佳时,网络会出现拥塞,这时丢包率会大大升高。
TCP为了可靠传输,出现丢包就会立刻重传,但是重传会带来更为严重的拥塞,之后丢包率又会再次升高,这时候无论怎么发送都会丢包,而一旦丢包TCP又会选择重传,前面的包还没丢完,后面的又跟上了,整个网络就瘫痪了。
重传加剧拥塞
因此,TCP有必要实现拥塞控制。
拥塞控制与流量控制常常会搞混:
流量控制是针对接收方而言的,对方的处理能力决定了发送方需要进行流量控制,本质上是为了实现可靠传输(试想,对方如果处理不了,而不断丢包,还能叫可靠传输吗?)
拥塞控制是针对整个网络环境而言的,若TCP有拥塞控制的能力,在有拥塞的征兆时就有效的减少数据段的发送,就会减少丢包带来的重传,可以节省整个网络环境的资源,也为了自身能更有效地完成传输任务。
两者的共同之处在于都需要通过减少发送数据而实现。
分组丢失:拥塞前兆
2.6.1 拥塞窗口
发送方的发送窗口大小除了被接收方的接收窗口大小限制,还被表示网络拥塞程度的拥塞窗口限制。
且发送窗口的大小取决于这两个窗口的较小值。
拥塞判断:
当出现以下两种情况时,发送方默认出现了网络拥塞,会实时的对拥塞窗口大小进行调整。
- 出现超时
- 接收方连续发送三个同样的确认包;【连续发送三个同样的,而发送方既然已经接到了确认包,就也会发送确认包,然而对方还发,肯定还是没收到,并且还发了两次,连续出意外的可能性很小,基本可以说明网络有一点问题。】
以上两种情况中1情况的可能性更大,更危险。
拥塞控制算法:
慢开始:
刚开始只使用cwnd=1试探,每次cwnd×2,【其实是每多收到一个确认,就根据这个确认多发一个请求,这样一累加就变成了指数增加(你可以思考一下)】
拥塞避免:
到了一定程度(ssthresh),会变成每次只增加一个拥塞窗口;
当出现超时,说明网络拥塞严重,直接打回原形,重新练级,且ssthresh降为超时时cwnd的一半。
快重传与快恢复:
当出现三次重传,说明还不算太坏,但是也需要控制,只会降到当前窗口一半,之后仍保持加一节奏。
路由器的丢包会造成TCP的超时,但是即使网络不拥塞,路由器仍然可能丢包:如,校验失败,路由器性能过低处理请求过慢等等。
路由器是造成TCP拥塞误判的最主要的因素。
另一种在网络层避免拥塞的方法:随机早期检测RED、
之所以这样做是防止丢弃太多同一台主机的包,造成这个主机的大量重传,从而间接使得网络更加拥塞。
因为, 重传加重拥塞。
3. TCP连接管理
3.1 连接建立:三次握手
握手的过程:【以A向B建立连接过程为例:】
- A发送建立请求包,包含以下信息:
syn = 1,表达我想建立连接;
序列号seq = x,x表示我当前发送报文的序列的第一个字节序号;
ACK=0,表示当前是第一个包,因此确认号ack失效;【注:这是之后需要报文段中所有ack唯一失效的一次】
B收到A的请求连接数据段后,会发送确认包。
同时,B也需要向A建立连接(因为TCP是全双工的,不建立双方的连接就没法保证确认机制的有效性。)
为了减少发送数据段的次数,B决定把这两次操作合并为一次:
1. syn = 1,表示B想建立连接;
2. seq=y,表示B发送的数据报第一个序号为y;
3. ACK=1,此时B还有确认A的请求的任务,因此确认号必须有效;
4.ack=x+1,表示A的请求我已经同意,并且A之前的x个数据已经全部收到,接下来从x+1开始传。
A收到了B的请求,自然要做出回应,答应B建立连接的请求:
- ACK=1,表示确认号有效;
- ack=y+1,表示B的请求我同意了,并且B的前y个字节全部收到,接下来从y+1开始传;
3.seq=x+1,按照B之前的要求,我从x+1开始继续传数据;- syn=0,syn只有在请求的时候才会置为1;此时双方都不再需要请求,因此sysn必须置为0;
综上:
之所以是三次,而不是两次或者四次,是因为必须包含两次请求消息,两次确认消息;
且中间B发出的确认消息和请求消息合并为1次发出了。
3.2 连接释放:四次挥手
以A率先结束连接为例:【所有的ACK=1,因为不管有没有接收到数据都需要发送确认消息,就拿之前发送的最后一次信息的序列+1作为确认,不是想让对方继续发信息,而是告诉对方我同意的意思】
A的数据已经发送完毕,因此需要释放连接;
- ack=v+1,表示自己成功收到v,想接收v+1之后;
2.fin=1,表示自己信息发送完毕,想断开连接;- seq=u,我当前发送最后一次序列,从u开始;
B收到了,但是B还有数据没发完,因此B不打算现在断开连接,但是他同意A单方向断开连接,现在AB之间是半连接状态
- ack=u+1,表示最后一次消息收到,我同意你断开连接;
2.seq=v,我想继续发,你接着收;
注:此时,A的发送窗口和B的接收窗口释放,不再维持。
中间可能掺杂多次B向A发数据,A发送确认的过程。但是与连接的释放没关系,请自行脑补。
与释放连接的四次报文就是这四次。
B信息传输完毕,也想断开连接。
ack=u+1, B继续使用A最后一次序列的后一位作为确认;
seq=w,表示B最后一次发送的序列从w开始。
fin=1,B表示想断开连接。
A同意B的请求,并发送确认。
- ack=w+1,表示你的请求和消息我都收到了,你挂吧。
2.seq=u+1,你想我发我就发;
通常情况下,到这里肯定就结束了。
然而可能出现一种情况:A发送的最后一个确认包B没有收到。
假如B没有收到,B会超时重传,A也就会重传;
但是,这时A还是不确定B有没有收到。
为了保证B能百分百收到,A会额外等待两个超时时长,若是超过两个超时时长B还是没有超时重发,A就认为无论无核B肯定是收到了,因此A撤销到接收缓存,通信结束。
4. TCP的有限状态机
有限状态机表示TCP协议设置时的各种状态之间的转换。
有限状态机是一个闭环,所有状态之间必定可以互相转换,且不存在某一个状态的请求不能被处理的情况。【即箭头必定指向一个目标,即其他状态】