文章目录
- UDP协议?
- 什么是校验和?
- 基于UDP的应用层协议(了解)
- TCP协议
- 确认应答(可靠性机制)
- 超时重传(可靠性机制)
- 连接管理(可靠性机制)
- 三次握手(重点)
- 四次挥手(重点)
- 三次握手和四次挥手时客户端和服务器的状态
- 滑动窗口(效率机制)
- 流量控制(效率机制)
- 窗口探测(效率机制)
- 拥塞控制机制(效率机制)
- 延时应答(效率机制)
- 捎带应答(效率机制)
- 粘包问题
- 异常情况处理
- TCP和UDP的区别
UDP协议?
UDP它是属于TCP/IP协议族中的一种。是无连接的协议,发送数据前不需要建立连接,因为不需要建立连接,所以可以在网络上以任何可能的路径传输,至于有没有传输到目的地,UDP是不关心的,所以,UDP它是天然支持广播的,就类似学校的广播,只需要将声音传递给每个学生即可,不需要学生的回复;
学习一个协议,最主要的就是去理解协议报文的格式
什么是校验和?
在网络传输中,因为一些外部因素的干扰,就可能会出现数据传输出错的情况,针对于这种情况,接收方就需要有办法能够识别出出错的数据,而校验和就是一种检验的手段。
举个例子,例如,你的妈妈告诉你说:“去,往超市给我买 西红柿,黄瓜,土豆,菠菜 这四种菜”,这里不仅告诉你了要买哪些菜,而且还告诉你了是四种,四种就好比一个校验和,所以,买完菜以后就可以检查一下是否是四种来确定是否买完了,所以这就是一种校验
在计算机中,校验和,其实本质上是一个字符串,体积比原始数据更小,但又是通过原始数据生成的,原始数据相同,得到的校验和就一定相同,反之,校验和相同,原始数据大概率相同,但是理论上也会存在不同的情况,而这种情况的概率也是非常低的。
如何基于校验和完成数据校验呢?
- 发送方,把要发送的数据整理好(假设为data1),通过一定的算法,计算出校验和checksum1
- 发送方把 data1 和 checksum1 一起通过网络发送出去
- 接收方,接收到数据(称为data2) 和 校验和checksum1,而 data2 可能会和 data1 不一样
- 接收方再根据 data2 按照相同的算法重新计算校验和,得到checksum2
- 对比 checksum1 和 checksum2 是否相同,如果不同,则认为 data1 和 data2 一定不相同,如果 checksum1 和 checksum2 相同,则认为 data1 和 data2 大概率是相同的
这里的计算校验和的算法都有:简单累加算法、md5 、CRC等
简单累加算法:把当前要计算校验和的数据,每个字节都进行累加,把结果保存到变量中,累加过程中,就算有溢出也没有关系,如果中间某个数据出现传输错误的情况,第二次计算的校验和和第一次就会不同,但是,这种算法有一定概率不太靠谱,因为也可能会因为比特位的原因,导致两个不同的数据得到相同的校验和,
md5算法:
md5有两个特点:
1.定长,不管原始数据多长,计算得到的md5,都是固定长度
2.分散,给定两个原始数据,就算是其中一个字节不同,得到的md5都会差异很大
3.不可逆,从原始数据计算 md5 很容易,但是将 md5 还原成原始数据,计算量就非常庞大了,理论上是不可行的;
基于UDP的应用层协议(了解)
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议
- DNS:域名解析协议
TCP协议
TCP报文格式:
下面针对上表中的每个属性进行讲解:
4位首部长度:一个数据报 = 首部(报头) + 载荷,首部长度也就是报头长度,表示该TCP头部有多少个32位bit(有多少个4字节),4位所表示的最大长度是15,所以报头就是60字节,而且TCP的报头是可变长的,这一点也就和UDP不同,UDP的报头固定就是8个字节,而TCP这里,在有选项的情况下,报头可以更长,
保留位: UDP有个问题,数据报的总长度就是64kb,报头固定就是八个字节,而TCP这里,就留了一个心眼,就是我现在不用,但是先占个位置,后面如果有需要,再使用。
6位标志位:
-
URG:紧急指针是否有效
-
ACK:确认号是否有效
-
PSH:提示接收端应用程序立刻从TCP缓冲区中把数据读走
-
RST:对方要求重新建立连接,所以携带RST标识的数据报也称为复位报文段
-
FIN:通知对方,本端要断开连接,携带FIN标识的数据报也称为结束报文段
关于以上标识的用法在下面进行讲解!
报文中的其他属性,均在下面各个机制中进行讲解!
TCP协议最大的特点就是:可靠传输(TCP初心)
这里说的可靠传输不是说数据能够100%的传输给对方,而是退而求其次,当发送方发出去数据后,能够知道接收方是否收到了数据,一旦发现接收方没有收到数据,就可以通过一系列的手段进行补救,而发送方是根据TCP中的确认应答机制,从而知道接收方是否收到了数据
确认应答(可靠性机制)
确认应答:发送方,把数据发送给接收方后,接收方就会给发送方返回一个应答报文(ACK),发送方如果收到这个应答报文了,就知道自己的数据是否发送成功了
后发先至
在传输数据时呢,可能会出现一种这样的情况:第二条数据比第一条数据优先到达,这就是后发先至;
对于后发先至的情况,TCP就要做两个处理:
- 确保应答报文和发出去的数据,能够对上号,也就是我先后发出的两条数据,你给我的响应报文也都是可以对上号的,不会出现歧义
- 如果出现先发后至的现象时,能够让应用程序这边仍然按照正常的顺序来理解
所以就用到了TCP报文中的 32位序号和 32位确认序号, 对发出去的数据进行编号,然后就可以根据这个序号进行应答,这样就可以对应起来了,而且在确认应答数据包里会有一个确认序号,就算发生了后发先至,也会根据确认序号对应答数据报进行排序,这样就避免了信息出错。就实现了TCP的可靠传输,所以确认应答就是实现可靠传输的最核心的机制
还需要注意的就是,TCP是面向字节流的,所以这里的序号也不是按照“一条两条”来编号的,而是按照字节进行编号的,每一个ACK中都带有对应的确认序列号,意思是告诉发送者,我收到了这些数据,下一次你从哪开始发,如下图(下图只是一个例子,并不是真实的情况)
而在TCP报文中,也使用了一个标志位来表示哪些数据包是普通数据包,哪些数据包是应答数据包:
如果这一位是 1,表示当前数据包是一个应答报文,此时该数据包中的“确认序号字段”就能够生效
如果这一位是 0,表示当前数据包是一个普通报文,此时数据包中的“确认序号字段”不会生效
超时重传(可靠性机制)
以上就是关于确认应答机制的讲解,确认应答它是一个比较理想的情况,但是如果网络传输过程中,出现丢包了,发送方就无法收到ACK了,那么就需要使用超时重传针对确认应答进行补充。
补充:为什么会丢包?
举个例子,比如我们在高速公路上开车时,会有收费站,在平时,高速上的车比较少,在收费站这里不会出现堵车的情况,但是,在国庆时,高速上收费站,就会出现堵车几个小时的情况,在网络上,数据包在传输时,会经过很多的交换机和路由器,而这些交换机和路由器就是“收费站”,在数据包非常多的情况下,在路由器这里就会出现“堵车”,而路由器的处理就会很粗暴,并不会把这些数据包给保存好进行处理,而是会把大部分的数据包直接丢弃掉,此时这个数据包就丢失了
由于丢包是一个随机事件,所以在TCP的传输过程中,丢包存在两种情况:
- 传输的数据丢了
- 返回的ACK丢了
无论出现上述的那种情况,发送方都会进行“重新传输”,第二次,第三次….随着次数的增加,那么出现丢包的概率就会非常小
假设,一次丢包的概率是 10%,那么两次传输都丢包的概率就是 10%*10%= 1%,所以,重传操作,大幅度提升了数据能够被传过去的概率 ,所以,当引入“可靠性”时,也是会付出代价的,这个代价就是:1.这个过程非常复杂,2,影响传输效率,所以这也就是UDP能够不被TCP完全取代的意义,在特别需要性能的场景下,UDP还是会比TCP更胜一筹
发送方何时进行重传?
发送方,发出去数据之后,会等待一段时间,如果在这个时间之内,ack来了,此时就认为数据到达了,如果超过了等待时间,就会触发重传机制~~,这里的初始等待时间,不同的系统上都不一定,也可以通过修改一些内核的参数引起这里的时间变化,而且等待的时间也会动态变化,每经历一次超时,等待的时间就会变长,这里的变长当然也不是无限的拉长,而是拉长到一定时间后,就会认为数据再怎么重传也没用了,就会触发TCP的重置连接操作。
还有一个问题就是:如果是ACK丢了,如果触发了超时重传,重新再传一次数据,那么在接收方这里就会收到两条同样的数据,所以针对这样的问题,TCP也已经解决了,TCP会有一个“接收缓冲区”,就是一个内存空间,会保存当前已经接收的数据,以及数据的序号,如果接收方发现当前发送方发来的数据已经存在于接收缓冲区中了,接收方就会直接把这个后来的数据给丢弃掉,也就进行了去重,就确保了应用程序读到的只有一条数据。
所以接收缓冲区不仅仅可以进行去重,还能进行排序,确保发送的顺序,和应用程序读取的顺序是一致的,这也就用到了序列号和确认序列号;
连接管理(可靠性机制)
三次握手(重点)
建立连接的过程就叫做:三次握手
TCP这里的握手,也就是给对方传输一个简短的,没有业务的数据报,通过这个数据包,来唤起对方的注意,从而触发后续的操作 ,挥手也是同样的操作
TCP的三次握手,TCP在建立连接的过程中,需要通信双方一共“打三次招呼”,才能够完成建立连接,如下图:
第一步:
假如 A 想要和 B 建立连接,A就会主动发起握手动作,A就会给B发送一个没有任何意义的数据包,也就是syn(同步报文段),这个同步报文段也是一个比较特殊的 TCP 数据包,因为它是没有载荷的(没有携带任何业务数据的),就是在TCP的报头里面有一个 SYN 标志位,在接收方这里,如果发现这个SYN标志位为1,就会知道这是一个同步报文段
第二步:
B 收到这个同步报文段后,就会使用TCP的应答机制给 A 返回一个ack(应答报文),告诉A你发送的数据包我收到了,同时呢,B 还会给 A 也发送一个 syn(同步报文段)
第三步:
A 收到 B 发来的 ack 后,就知道了 第一次给 B 发的 syn同步报文段B已经收到了,同时也收到B发来的syn后,就会再给B返回一个ack应答报文,然后,B就能知道给A发的ack 和 同步报文段 A已经收到了
建立连接的过程,其实是,通信双方都要给对方发起 syn, 也都要给对方返回 ack,总的来讲,一共是四次交互了,但是,中间两次恰好可以合并成一次;
经过上述过程后,握手就完成了,此时,A 和 B 就记录了对方的信息,连接就完成了;
三次握手是要解决什么问题?通过四次握手是否可行?通过两次握手是否可行?
因为TCP的初心就是可靠传输,而可靠传输的核心机制是确认应答 和超时重传,要进行这样的机制就需要有一个大前提就是当前的网络必须是通畅的,如果网络都出现故障了,那么就不可能实现可靠传输了。
所以三次握手的第一个核心作用就是:投石问路,确认当前网络是否是通畅的。第二个作用就是:要让发送方和接收方都能确认自己的发送能力和接受能力
举个例子:
例如我在和女朋友双排打游戏,而且我们都带着耳机和麦克风进行沟通,所以,在打游戏前我就要和女朋友测试以下耳机和麦克风是否可用,所以,第一、我就会先hello 一声,然后她听见后,站在它的角度她就知道了她的耳机和我的麦克风是没问题的,但是我是不知道的,第二、然后她就会再喊 hello hello , 然后我就可以听见了,这时候,站在我的角度,我就知道了我的耳机和我的麦克风以及她的麦克风,她的耳机都是没问题的,但是她还不知道她的麦克风能不能用呀,所以,第三、我就会再 hello hello hello 几声,这时候,她就知道了她的麦克风,她的耳机,以及我的麦克风,我的耳机都是可用的,所以接下来就可以开黑了~~
如果是两次握手的话,最后的 hello hello hello 我不喊,她是不知道他的麦克风是否是能用的,而如果是四次握手的话,可以是可以,但是就已经是画蛇添足,有点多余了!!!
第三个核心作用:让通信双方在握手的过程中,针对一些重要的参数进行协商
举个例子,就比如我们的手机和充电头的功率,假如我们的手机是支持90w的功率,充电头支持60w的功率,当插上电以后,手机中的芯片和充电头的芯片就来握手,在这个过程中就会针对使用多大的功率进行协商,以及确定最后的使用功率
在这握手的过程中,协商的信息是有好几个的,例如最典型的就是,TCP通信过程中的序号是从几开始,每次连接时,一般都会协商一个比较大的,和上次不一样的值
这样搞的目的也就是为了避免“前朝的剑,斩本朝的官”,因为有的时候,网络如果不太好,客户端与服务器就可能会断开连接,再重新建立新的连接,此时旧连接时发的数据就可能在建立好的新连接中发过来,而这种迟到的数据,应该丢弃掉,以防止上个朝代的数据影响到本朝代的逻辑,所以,就是通过序号的设定规则来区分这个数据是上个朝代的还是这个朝代的。
四次挥手(重点)
断开连接的过程叫做:四次挥手
建立连接一般都是客户端发起的,谁发起连接,谁就是客户端,而断开连接,客户端和服务端都可以发起。
假如,A 想要和 B 断开连接,A 就会给 B 发送一个 FIN 结束报文段,然后 B 就会给 A 再返回一个 ACK 和一个 FIN,最后 A 就会再返回一个 ACK,此时就断开了,这个时候,就相当于 A 和 B 都把对端的信息删除了
这里的 四次挥手 和 三次握手 不同,此处的四次挥手,是否能将中间的两次合二为一?这个是不一定的!
不能合并的原因是:ACK 和 第二个 FIN 的触发时机是不同的,因为 ACK 是系统内核响应的, 在 B 这边收到 FIN 后, 就会立即返回 ACK, 而第二个 FIN 是应用程序的代码触发的,B 这边的 FIN 是在B这边的socket 对象被close 时才会被发起的,从 B 收到 FIN(同时返回ACK),再到执行 close,这中间要经历多少代码,经历多长时间,是不确定的,就看代码是怎么写的了。而三次握手中的 ACK 和 SYN 都是内核触发的,是同一时机,所以可以合并!如果B这边的代码没有close或没执行到,是不是第二个FIN就一直发不出去?
首先呢,这个四次挥手它是一个正常流程的断开连接,如果说,没有挥完四次,它就是一个异常的流程,也是会断开连接的。
以上是不能合并的原因,能合并的原因是:在TCP中还有一个机制,延时应答机制,它能够拖延 ACK 的回应时间,一旦ACK滞后了,就有机会和下一个FIN合在一起了!
三次握手和四次挥手时客户端和服务器的状态
下图是TCP经历三次握手和四次挥手的一个状态转换图
listen状态:服务器状态。服务器这边将 socket 创建好,并且把端口号绑定好,此时就会进入 listen 状态,此时就允许客户端随时建立连接了。
ESTABUSHED 状态:客户端、服务器都会有的状态,表示连接建立完成,接下来就可以进行正常通信了
服务器端状态转化
【CLOSED->LISTEN】 服务器调用listen后进入LISTEN状态,等待客户端连接;
【LISTEN -> SYN_RCVD】一旦监听到连接请求(同步报文段),就将该连接放入内核的等待队列中,并向客户端发送ACK确认报文。
【SYN_RCVD -> ESTABLISHED】服务端一旦收到客户端的应答报文,就进入ESTABLISHED状态,就可以进行读写数据了
【ESTABLISHED -> CLOSE_WAIT】当客户端铸锻关闭连接(调用close),服务器会收到结束报文段,服务器返回应答报文并进入CLOSE_WAIT状态
【CLOSE_WAIT -> LAST_ACK】进入CLOSE_WAIT后说明服务器准备关闭连接,当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器就进入了LAST_ACK状态,进行最后的确认,等待最后一个ACK的到来
【LAST_ACK -> CLOSED】服务器收到FIN的ACK后,进入CLOSED,彻底关闭连接
客户端状态转化
【CLOSED -> SYN_SENT】客户端发送同步报文后,进入SYN-SENT
【SYN-SENT -> ESTABLISHED】发送成功后(收到syn+ack),进入ESTABLISHED状态,开始读写数据
【ESTABLISHED -> FIN_WAIT_1】客户端主动调用close时,向服务器发送结束报文段,同时进入FIN_WAIT_1
【FIN_WAIT_1 -> FIN_WAIT_2】客户端收到服务器对结束报文的确认(返回的ACK),则进入FIN_WAIT_W,开始等待服务器的结束报文段
【FIN_WAIT_2 -> TIME_WAIT】客户端收到服务器发来的结束报文段,进入TIME_WAIT,并发出最后的ACK
【TIME_WAIT -> CLOSED】客户端要等待一个2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入CLOSED状态
为什么TIME_WAIT要等待2MSL???
- MSL是TCP报文的最大生存时间,因此TIME_WAIT持续存在2MSL的话
- 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到来自上一个进程的迟到的数据,但是这种数据很可能是错误的);
- 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失,那么服务器会再重发一个FIN。这时虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK);
滑动窗口(效率机制)
确认应答机制,超时重传机制,连接管理机制都是在保证TCP的可靠性,由于TCP引入了可靠传输,多出了一些等待ACK的时间,所以单位时间内传输的数据就会减少,影响了传输效率,所以,就使用了滑动窗口来降低可靠传输对性能的影响;
如下图:
一次发送1000个字节的数据,那么在主机A这边还要再一直等待主机B这边的ACK,这种一发一收的情况效率就比较低
如下图:使用滑动窗口来批量传输数据,
- 一批一批的传输,如上图,四个段为一批,在传输前四个段时,不需要等待任何ACK,直接发送,这一批传输完后,开始接收ACK,
- 窗口大小就是指每一批传输的最大数据量,上图窗口的大小就是4000字节
- 收到第一个ACK后,滑动窗口向下移动, 就这样,同时返回ACK,同时发送接下来的数据,这个窗口中,始终保持着4000字节的数据
- 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答,只有确认应答过的数据,才能从缓冲区中移除,如果一直没有移除的话,就表示丢包了,至于发生丢包后的措施在下面讲解;
- 窗口越大,则网络的吞吐率就越高
**如果出现丢包现象,该怎么办?**针对这种现象就有两种情况:
情况1:传输过程中,ACK 丢了
这种情况不需要我们进行任何处理,这时候,确认序号就起到了大作用,确认序号表示的含义是,当前序号之前的数据已经确认收到了,下一个应该从序号这里继续发送,就比如下图,2001的ack丢了,但是3001的ack到了,此时就表示3001之前的数据都已经确认传输成功了;包含 2001 数据的情况!!!
情况2:传输过程中,数据丢了
例如下图,1001~2000 的数据丢了,所以在返回 ACK 时,无论当前传输的数据是多少,都会一直索要 1001,同时,窗口中的数据还会继续发送,等到主机A察觉到 1001 这个数据丢失了,就会重传 1001~2000, 传输完之后,就会继续从下图 8001 开始,因为,虽然 1001 丢了,但是,其他的数据还是在正常的传输,当丢失的数据补上之后,就会继续按照当前应该传输的数据继续向下以此类推;
TCP针对于这一块会有动态的调整,如果通信双方传输的数据量比较小,也不频繁,就仍然是普通的确认应答和普通的超时重传;如果通信双方,传输的数据量比较大,也比较频繁,就会进入到滑动窗口模式;
通过滑动窗口的方式传输数据,效率是会得到提升,窗口越大,传输的效率就越大,但是,窗口设置的太大也是不太好的,因为,如果传输的速度太快,就可能使接收方处理不过来,此时,接收方就会出现丢包,发送方还得重传;为了控制发送方的发送速度,在接收方就利用流量控制机制进行调整;
流量控制(效率机制)
流量控制是站在接收方的角度,反向制约发送方的传输速度,发送方的发送的速度,不应该超过接收方的处理能力;
那么它是如何对窗口进行调整的呢?
答案:首先,发送方发送数据到接收方,数据到达接收方的系统内核中,因为TCP Socket对象带有接收缓冲区,所以,发送方给接收方发送的数据就会放在接收缓冲区中,随后,接收方的应用程序从接收缓冲区中读取数据,读取过的数据就会从缓冲区中删除,所以,想要知道接收方处理能力的速度,通过接收缓冲区剩余的大小就可以间接的知道,如果接收缓冲区空间大,就意味着处理能力强,剩余空间小,处理能力就较弱,从而动态的调整滑动窗口的大小;
在返回应答报文中,接收缓冲区剩余空间的大小也会记录在报文中的16位窗口大小这个属性中,但是,他并不是只有16位的大小,在报头中的选项部分里有一项是“窗口扩展因子”,通过这个窗口扩展因子让16位窗口大小表示一个更大的值
窗口探测(效率机制)
在滑动窗口机制下传输数据,它是批量传输的,在这个过程中,如果接收方还没有来得及处理接收缓冲区中的数据,那么,发送方每传过去一段数据,接收缓冲区剩余的空间就减小一些,等到剩余空间为0时,此时,发送方就会意识到现在不能再继续发送数据了,要等待接收方处理了一些数据后再继续发送,而此时,发送方具体要等待多长时间呢?这个发送方是不知道的,所以发送方就会周期性的给接收方发送一个不带任何业务数据的 “窗口探测包”,这个探测包就只是为了触发ACK,为了查询接收方这边的接收缓冲区剩余空间大小;
拥塞控制机制(效率机制)
在通信过程中,是需要通过很多交换机和路由器进行转发的,在这个转发的过程中,任何一个节点,处理能力达到了上限,都可能会对发送方产生影响,都可能会影响到可靠传输,就算是接收方还有处理数据的能力,但是,如果中间的某个节点不给力,那么仍然是会影响整个通信的情况,所以流量控制机制它考虑的接收方的处理数据的情况,拥塞控制机制就衡量了通信过程中,中间节点的情况;
拥塞控制机制具体是什么样的:
因为流量控制机制是通过接收缓冲区剩余的大小来对接收方的处理能力进行了量化,而通信过程中,数据在中间节点上进行转发时,这个过程是更加复杂的,很难进行直接量化,所以拥塞控制就通过 “实验” 的方式,来找到一个合适的值;
例如:先让发送方按照比较低的速度,通过小一点的窗口发送数据,如果数据传输的过程非常顺顺利,没有丢包,在尝试使用更大的窗口,更高的速度进行发送,就这样一点一点的去试探这个底线在哪里,随着窗口大小不停的增大,达到一定程度,可能某个中间节点就会出现问题,此时这个节点就可能会出现丢包,之后,发送方发现丢包了,就再把窗口大小调整小,就这样来回的进行调整窗口大小,逐渐达成 “动态平衡”,这样的一个过程就被称为 “拥塞控制”。
下面通过图可以更直观的看出动态变化现象:
- 在慢开始阶段,发送的次数会比较慢,窗口的大小会按照指数规律(翻倍)进行扩大
- 达到一定的阈值后,也就是图中ssthresh的初始值 16,指数增长就会变成线性增长
- 线性增长到一定程度,中间某个设备可能就顶不住了,就会出现丢包现象
- 一旦触发丢包,也就是图中网络阻塞时,就会把窗口再缩小,重新进行前面的 慢开始 -> 指数增长 ->线性增长,并且会根据当前丢包窗口的大小,重新指定开始线性增长的阈值,也就是图中的新的 ssthresh 值12,就是为了避免指数增长时,一下就达到了丢包的极限;
上图是TCP经典版本的拥塞控制,TCP对拥塞控制又进行了一个改善,如下图:
原来的版本它把之前积累的速度一下子给清零了,继续又从指数增长的过程开始了,但是,我们已经知道了阈值前的窗口大小是不会丢包的,所以就没有必要再从头开始了,就可以直接从新设定的阈值的地方开始线性增长,这样也就提高了效率;
流量控制 和 拥塞控制 都是在限制发送方的滑动窗口大小,最终发送的窗口大小也是取决于 流量控制 和 拥塞控制 中的窗口的较小值。
延时应答(效率机制)
当 发送方 发送数据给 接收方 时,接收方就会立即返回 ACK 给发送方,这是一种正常情况,但是,也有的时候,为提高效率,发送方发送数据给接收方后,接收方等一会儿再返回ACK给发送方,此时就是 “延时应答”。
延时应答机制本质也是为了提高传输效率,因为发送方的窗口大小,就是传输效率的关键,而流量控制这里,就是根据接收缓冲区的剩余空间,来决定发送速率的,如果能够有办法,让流量控制这里计算的值较大,让发送方的窗口更大点,发送速率更快点(而这个变大的前提也是能够让接收方能处理过来),所以就可以通过延时返回 ACK ,给接收方更多的时间,来读取接收缓冲区的数据,此时接收方读完数据以后,接收缓冲区的剩余空间就变大了,返回的窗口大小也就可以变得更大了;
捎带应答(效率机制)
捎带应答是在延时应答的基础上,进一步的提高效率;
在网络通信中,往往都是 “一问一答”的通信模型,如下图:
当 A 给 B 发送了一个Request请求后,在 B 这边,系统会立即返回一个 ACK给 A,而响应的业务数据 Response 需要等应用程序执行完在返回,所以这两个返回的时机是不同的,由于 TCP 引入了延时应答,所以,ACK 不一定立即返回,可能会等一会儿,而在这个等一会的过程过程中, B 正好将 Response 给计算好了,计算好之后,就会将 Response 返回,与此同时,也就会将刚才要返回的 ACK 也顺带带上,这就是 “捎带应答机制”,本来是要传输两个tcp数据包(进行两次封装分用),因为捎带机制,就可以将两个包合并成一个,所以也就提高了效率;
粘包问题
因为 TCP 是面向字节流的,所以,这里就会出现一个问题-> “粘包问题”,而“粘包问题”并不是TCP独有的,而是所有面向字节流的机制都有类似的情况,此处的 “包” 指的是应用层数据包,如果同时有多个应用层数据包被传输过去,此时就容易出现粘包问题;
如下图讲解:
假设,AAA,BBB,CCC,是三个应用层的数据包,发送给 接收方B 后,会放在接收缓冲区中,因为此时面向的是字节流,所以在接收缓冲区中是按照字节的形式紧紧挨在一起,接收方的应用程序在读取数据时,可以按照读取1个字节,2个字节……N个字节的方式读取,但是最终的目的是为了得到完整的应用层数据包,但是,此时 B 的应用程序就不知道缓冲区里的数据,从哪里到哪里是一个完整的应用层数据包,在解析数据时,就不知道该如何解析了,这样的一个情况就是“粘包问题”
相比之下,像 UDP 这样面向数据包的通信方式,就没有上述问题;因为UDP通信过程中是以数据报为单位的,UDP 的接收缓冲区中,相当于是一个一个的 数据报对象,应用程序就可以以数据报为单位进行读取
如何解决粘包问题?
答案:核心思路:通过定义好应用层协议,明确应用层数据包的边界,规定这个边界就有两种方式:
1、引入分隔符
在每一段数据的最后都加一个\n,表示\n前的数据是一个完整的数据包
2、引入长度
在数据的头部加上整个数据的长度,在接收方读取数据时,根据头部的数据长度,读取完整的数据包
异常情况处理
如果在使用TCP的过程中出现意外,会如何处理?,例如以下的几种异常情况:
1.通信过程中,进程崩溃
进程异常终止,对应的文件描述符表也就释放了,此时就会触发四次挥手,就会触发 FIN, 对方收到之后,自然就会返回 FIN 和 ACK,这边再进行 ACK,然后双方断开连接,TCP的连接是可以独立于进程存在的,进程没了,TCP连接不一定会消失
2.主机关机(正常流程)
进行关机时,就会先触发强制终止进程操作,这时候就相当于回到了上面第一种情况了,但是,在关机时,不仅仅是进程没了,整个系统也可能关闭了,如果在系统关闭之前,ACK 和 FIN 到了,此时还是进行正常的四次挥手,如果系统已经关闭了,ACK 和 FIN 迟到了,无法进行后续 ACK响应了,站在对端的角度,对端以为是自己的 FIN 丢包了,就会重传 FIN, 重传几次后都没有响应,自然就会放弃连接。因为四次挥手就是一个友好的断开过程,在断开前,会打一声招呼,如果在多次打招呼后都没有得到响应,也是会断开连接的
3.主机掉电(非正常)
主机掉电,是一瞬间的事情,就来不及杀死进程,来不及发送FIN,一下子就没了,站在对端主机的角度,对端是不知道这个掉电的事情的。
第一种情况:
1、如果是 发送方 发送数据,接收方掉电,发送方发送后就会等待 ACK,等不到ACK后,就会触发超时重传,如果一直等不到ACK,就会触发 TCP 的连接重置功能,发起 “复位报文段”,也就是将 RST 进行标记,这个复位报文段发过去之后,就期望对方返回一个 ACK, 但是此时接收方已经掉电了,无法返回这个ACK,此时就没有产生效果,此时发送方这边就会自动放弃连接
第二种情况:
2、如果接收方正在接收数据,发送方掉电,此时,接收方还在等待数据到达,但是,发送方已经掉电了,数据发送不过去,而在接收方的角度,就无法区分发送方已经掉电了还是发送方根本没发送数据,针对这种情况,TCP 还提供了 “心跳包机制”,在接收方这里,它会周期性的给发送方发送一个特殊的,不携带业务数据的数据包,并且期望对方返回一个应答。如果对方没有应答,并且重复了多次之后,仍然没有,就会视为对方挂了,此时就可以单方面的释放连接了;
4.网线断开
这里的网线断开和主机掉电是非常类似的,假设 A 正在给 B 发送数据,一旦网线断开,A 就会触发 超时重传 -> 连接重置 -> 单方面的释放连接; B 就会触发心跳包机制 -> 发现对端没有任何响应 -> 单方面释放连接。
TCP和UDP的区别
1.TCP是有连接的,UDP是无连接的
这里的连接是抽象的概念,这种连接是指建立连接的双方,各自保存对方的信息;
按照TCP协议进行通信时,发送数据前要建立连接,连接完成之后才能进行通信
UDP是无连接的协议,发送数据前不需要建立连接,是没有可靠性
举个例子:
TCP通信可看作打电话:
李三(拨了个号码):喂,是王五吗? 王五:哎,您谁啊? 李三:我是李三,我想给你说点事儿,你现在方便吗? 王五:哦,我现在方便,你说吧。 甲:那我说了啊? 乙:你说吧。 (连接建立了,接下来就是说正事了…)
UDP通信可看为学校里的广播:
播音室:喂喂喂!全体操场集合
2.TCP是可靠传输,UDP是不可靠传输
网络通信过程中,A给B发送的数据并不是100%就可以发送成功的,因为,再牛逼的技术,也敌不过挖掘机的一铲子,如果你传输数据的光纤断了,那么这是肯定不会传过去的,或者说,就算是无线通信 ,它们虽然牛逼,但是它们数据传输的速率/稳定性,是一定不如有线的,而且在无线通信过程中,意外因素也会更多;
所以,在A给B发消息时,B这一方是否接收到了消息,A自己是可以感知到的(根据确认应答机制),进一步的,如果发送失败就可以采取一定的重传措施之类的,所以,虽然不能达到100%,但是也采取了一些重传机制进行弥补,就会非常可靠
而UDP就没有内置可靠传输,它就是消息发出去后,UDP就继续做自己的事情,就才不会管你到底有没有成功。
既然可靠传输听起来这么美好,为啥UDP也不搞一个呢?
因为,这就像是我们生活中,想要更好的东西,需要更高的代价去交换的,而这里的可靠传输要付出的代价就是:
1.机制更复杂
2.传输效率会降低
3.TCP是面向字节流的,UDP是面向数据报的
网络通信中,TCP是以字节为单位进行传输的,比如要传100字节的数据,它可以每次传10字节,分十次传完,也可以每次传50个字节,两次传完,也可以一次传完,这就是字节流的意思。
UDP是以数据报为单位进行传输的,数据报的大小只有64kb,并且不能够拆分和合并
4.TCP和UDP都是全双工的
一个信道,允许双向通信,就是全双工
一个信道,只允许单向通信,就是半双工
代码中使用一个Socket对象,就可以实现发送数据,也能接收数据
而UDP就没有内置可靠传输,它就是消息发出去后,UDP就继续做自己的事情,就才不会管你到底有没有成功。
既然可靠传输听起来这么美好,为啥UDP也不搞一个呢?
因为,这就像是我们生活中,想要更好的东西,需要更高的代价去交换的,而这里的可靠传输要付出的代价就是:
1.机制更复杂
2.传输效率会降低
5.TCP只支持点对点通信,UDP支持一对一、一对多、多对一、多对多;