1.TCP协议
TCP,即Transmission Control Protocol,传输控制协议。人如其名,要对数据的传输进行一个详细的控制。
TCP协议段格式
5.16位窗口大小:
6.16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不光包含TCP首部,也包含TCP数据部分。
7.16位紧急指针:标识哪部分数据是紧急数据;
8.40字节头部选项:
2.TCP原理
TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率。
这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。
确认应答机制(安全机制)
TCP将每个字节的数据都进行了编号,即为序列号。
每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发。
超时重传机制(安全机制)
但是,主机A未收到B发来的确认应答,也可能是因为ACK丢失了;
因此主机A会收到很多重复数据。那么TCP协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号,就可以很容易做到去重的效果。
那么,如果超时的时间如何确定?
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
连接管理机制(安全机制)(TCP如何建立连接,TCP如何断开连接)
在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接
TCP本来都已经保证可靠传输了,为啥还得三次握手,握2次,握4次行不行?
三次握手就是在投石问路,确认当前网络环境是否具备能够进行可靠传输,如果网络不通畅,后续会出现大规模丢包和重传,三次握手也是在进一步验证,通信双方发送能力和接收能力是否都正常。协商一些重要参数。
如果握俩次会出现以下情况:
很明显俩次握手不会保证通信正常!
如果握四次会出现效率太低:
四次挥手:
当客户端触发FIN之后,服务器只要收到FIN,就会立即返回ACK(内核完成的)
当服务器的代码中运行到socket.close()操作的时候,就会触发FIN~
谁主动断开连接,谁进入TIME_WAIT
建立连接,一定是客户端主动发起,断开连接,可能是客户端,也可能是服务器主动发起
TIME_WAIT这个状态会脱离进程存在,存在的目的就是为了处理最后一个ACK丢包之后重传问题
传输的过程中,任意的包都是可能会丢的~
其中第一组FIN或者ACK丢了,此时A都是没有收到ACK,A就会重传FIN
其中第二组FIN或者ACK丢了,此时B都是没有收到ACK,B就会重传FIN
因此A这边回复完最后一个ACK之后,不着急释放,先等一等
等一段时间之后,确保当前FIN不被重传了,然后才真的释放连接。
TIME_WAIT等待的时间叫做2MSL(MSL就是网络俩点之间消耗的最大时间)
此处的MSL的值也是可以配置的。
服务端状态转化:
客户端状态转化:
CLOSE_WAIT
一般而言,对于服务器上出现大量的 CLOSE_WAIT 状态,原因就是服务器没有正确的关闭 socket,导致四次挥手没有正确完成。这是一个 BUG。只需要加上对应的 close 即可解决问题。
滑动窗口(效率机制)
刚才我们讨论了确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差。尤其是数据往返的时间较长的时候。
改进:
那么如果出现了丢包,如何进行重传?这里分两种情况讨论。
情况一:数据包已经抵达,ACK被丢了。
这种情况下,部分ACK丢了并不要紧,因为可以通过后续的ACK进行确认;
情况二:数据包就直接丢了。
这个主机A连续发了很多数据,
1-1000
1001-2000 在这个过程中,虽然B在不停的收到新的数据
2001-3000 但是B一直在向A索要1001这个数据~~
在B反复的几次ACK都是在索要1001之后,此时A就明白了1001这个数据怕是 丢了~~
就会触发重传机制~~
此时就会把1001进行重传~~
一旦把1001重传了之后,此时主机B就把缺失的这块数据给填补上了~~返回的 就是7001了.
是否要返回2001 ??不需要的~~7001其实就涵盖了之前收到的一系列数据~~
当B一直返回1001的时候,A仍然在努力的往后发数据.
虽然B嘴上要的是1001,但是B的心里仍然默默的把A付出的这些数据都记住 了~~
当1001这里填补上之后,B检查自己的接收缓冲区,就发现,7001之前的数据都 齐了于是返回的ACK就直接是7001了~~
这种机制被称为 "高速重发控制"(也叫 "快重传")。
流量控制(安全机制)
对滑动窗口进行了制约
滑动窗口窗口越大,就认为传输速率越高~~滑动窗口也不是越大就越好.
可靠性是第一要务I如果发的太快,接收方顶不住了,,额外发出去的数据大概率还是要丢包=>触发了超时重传.….
也不是越大越好,一定是合适才是最好的~~
发送方的发送速率要和接收方的处理速率相匹配.
流量控制,主要是根据接收方处理数据的能力,来制约发送方的滑动窗口大小.发送方的滑动窗口大小是变化的(不是固定)
如何衡量接收方的处理速率?处理的速率取决于应用程序,调用socket api读数据的速度~~
接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制(Flow
Control);
这里就用到数据结构中的阻塞队列,和生产者消费者的阻塞队列一样起到缓冲作用。
接收端如何把窗口大小告诉发送端呢?回忆我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息;那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?
实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是 窗口字段的值左移 M位;
拥塞控制(安全机制)
相比于流量控制,拥塞控制是更复杂的~~
流量控制只考虑接收端一个人~~
拥塞控制考虑整个链路.这个链路上有多少个设备,这些设备都是啥情况??相当复杂的话题~~
由于这个中间的链路非常复杂.
拥塞控制的解决方案,是吧中间的整个链路视为一个整体~~通过“实验”的方式,找到一个合适的发送窗口大小~~
不断的尝试不同的窗口大小,在保证可靠性(不丢包)的前提下,尽可能的提高发送速度~~
拥塞控制会设置出一个"拥塞窗口”这样的指标.通过拥塞窗口来影响到滑动窗口的窗口大小~~
拥塞窗口也是动态变化的.刚开始用一个比较小的值(让发送方发的慢点)如果通信非常顺利,也没有丢包,就会逐渐放大拥塞窗口~~
加快发送的速度.同时密切监测丢包情况.如果加到一定程度了,发生丢包,意思就是网络链路已经顶不住~~立即再减小窗口大小让速度再慢下来如果不丢包了,再逐渐加码~~~反复重复上述过程,拥塞窗口就会逐渐稳定到某个合适的值上~~
这样的动态变化是非常有意义的~~网络环境非常复杂~~导致进行网络传输的时候很容易出现一些突然情况~~
拥塞控制会影响到滑动窗口
流量控制也影响滑动窗口~
虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。
因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。
TCP引入 慢启动 机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;
流量控制:限制滑动窗口大小(根据接收方的处理能力,接收方的接收缓冲区剩余空间大小)
拥塞控制:限制滑动窗口大小(根据发送方接收中间的传输链路的拥堵情况,来确定的)
少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案。
延迟应答(效率机制)
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么?肯定也不是;
具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms;
捎带应答(效率机制)
TCP四次挥手,有没有可能是三次呢?
有可能,因为捎带应答!
粘包问题(TCP机制延伸出来的问题)
所谓的粘包问题,指的是,当有多个TCP数据包到达的时候,如果不显式的约定包和包之间的边界,就很容易对数据产生混淆
任何的面向字节流传输机制,都会涉及到这个粘包问题。
(读写文件)
粘包,粘的是应用层数据报,只要能够区分清楚,应用层数据报和报之间的边界,就可以区分了
靠的就是应用层协议的设定!
如果我们是使用纯TCP写客户端服务器程序,并且自己从零开始设计应用层协议,这个时候就必须特别注意粘包问题 .
重新审视一下Http(应用层协议)
Http是否存在粘包问题?Http是如何解决的粘包问题
处理粘包问题的俩种方案:
现成的库和框架,已经帮我们把粘包问题都处理好了~~大多数情况下,咱们也不太会遇到这个问题~~但是如果我们是需要自己动手开发一个网络相关的库/框架,大家一定要注意到这里粘包问题的效果
异常机制
建立好连接的双方,在通信的过程中,遇到了一些突发状况,如何处理?
1.进程终止
A B 其中某个进程突然终止(崩溃了,或者被强制杀死)
如果直接杀死进程,看起来是触不及防,但实际上操作系统是早有准备~~
操作系统会释放这个进程的相关资源. (TCP这里依赖了一个socket文件,操作系统就会自动的关闭这个socket文件)这个自动关闭的过程基本类似于socket.close()【触发四次挥手】
2.机器重启
按照操作系统的既定流程重启~~
点击系统的关机/重启按钮
就会由操作系统,先把当前所有的应用程序,先强制杀死~~
—杀进程,就和上面的1是一样的了!!就会释放socket 文件,同时触发FIN
3.机器掉电/网线断开
UDP和TCP对比
TCP:可靠性
UDP: 效率更高(分布式系统中,同一个机房内部的多个机器之间的通信,一个机房里网络结构比较简单,带宽也比较充裕,这个时候丢包的概率是不大的)