UDP协议可参考文章: 传输层—详解UDP协议
TCP,即Transmission Control Protocol,传输控制协议
(人如其名,要对数据的传输进行一个详细的控制)
十大机制:
①确认应答(可靠性机制)
②超时重传(可靠性机制)
③连接管理(可靠性机制)
④滑动窗口(效率型机制)
⑤流量控制(可靠性机制)
⑥拥塞控制(可靠性机制)
⑦延时应答(效率型机制)
⑧捎带应答(效率型机制)
⑨面向字节流(粘包处理)
⑩特殊情况 (异常处理)
①有连接:客户端和服务器之间,利用内存保存对方一端的信息,当双方都保存这个信息之后,就能建立连接了,对于TCP而言,必须要建立连接才能使用
举例:就像打电话一样,对方不接通,你们是无法通信的
②可靠传输:A给B发的消息不是100%能发到的,但是A会尽可能的把消息传给B,假设传输失败,A也能感知到失败;假设传输成功,A也能知道自己传输成功了
举例:就像打电话一样,当你说了话但是对方没有响应时,你就会知道此时信号有问题
(可靠传输会使传输效率降低)
③面向字节流:读写的基本单位是字节,类似于文件操作
④全双工:一个通道,可以双向通信
举例:类似于车道
⑤无大小限制
(比UDP好的地方,传输数据大小没有限制)
⑥数据传输过程出错,即发送方和接收方的校验和不同,会重新发送
使用TCP的API可以编写网络编程,详解看Java初识网络编程
TCP协议段格式=TCP报头+TCP载荷数据
TCP报头就包含源端口号、目的端口号、序列号、确认序列号、TCP4位报头首部长度、保留位、6个标志位、窗口大小、检验和、紧急指针、选项
TCP载荷就是需要传输的数据,即应用层完整数据
①源端口号:标识发送端的端口
(2个字节)
②目的端口号:标识接收端的端口
(2个字节)
①序列号:当前主机,给发送数据的每个字节数据按顺序依次编号,通过查看序列号和确认序列号可以确定数据传输是否乱序,避免出现“后发先至”的情况
(4个字节)
②确认序列号:用于应答报文的字段属性,就是服务器确认收到数据后返回的序列号
含义:表示确认序列号之前的数据我都收到了,接下来从确认序号这个编号开始发数据
(4个字节)
(1)一般来说,我们在TCP报头中只需要知道第一个字节的序列号,再结合报文长度,就能知道每个字节的编号
(2)初始序列号为客户端随机产生的值,一般初始序列号和确认序列号都从0开始
(3)服务器接收后,确认序列号为收到数据的最后一个字节编号+1
(这里大家可能会有点懵,先记住,后面会结合TCP机制详细说明序列号重要体现哪!)
TCP报头首部长度:它用来表示TCP报头长度,我们需要知道TCP的报头是“变长”的,它不像UDP一样固定是8个字节,这里是以4字节为一个单位
(它是4位,即4bit,4bit=2⁴=16,即数据范围是0-15,又因为4字节为一个单位,15*4=60)
因此,TCP报头最大长度是60个字节
保留位:提高扩展性;给未来留下可以升级扩展的空间
(虽然TCP大概率也不会用,但是嘛!我可以不用,你不能没有)
六个标志位
(每个标志位占1bit)
①ACK:判断当前报文类型和确认序列号是否有效
(一般用ACK指代应答报文)
(1)当ACK=0,表示这是一个普通报文,此时只有32位序列号是有效的,确认序列号无意义
(2)当ACK=1,表示这是一个应答报文,此时序列号和确认序列号都是有效的
普通报文就是发送端的报文,它跟应答报文的序号之间是没有任何关联关系的
(一般我们不在意应答报文的序列号,只在意它的确认序列号,因为那决定了是否收到数据)
(ACK是由内核控制的,收到FIN就会立即返回ACK)
②RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
(当RST=1时,就表示此时要重新建立连接)
③SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
(当SYN=1时,就表示此时一台主机向另一台主机申请要求建立连接)
④FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
(当FIN=1时,就表示此时一台主机A向另一台主机B通知本主机A要关闭连接了)
(FIN是由应用程序代码触发的,调用socket.close()或者进程结束就会触发FIN)
⑤URG:紧急指针是否有效
⑥PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
表示接收方缓冲区的剩余空间大小
只适用于ACK报文的字段属性
(2个字节)
将这个数字返回给发送方,就可以作为发送方下一轮窗口的大小依据
2个字节,不一定意味着接收缓冲区空间最大就是64kb,我们可以看到报头里面还有一个叫"选项",这个"选项"里面又有个选项,叫窗口大小扩展因子,实际的窗口大小是16位窗口大小左移个扩展因子位,即窗口大小*扩展因子,能表示的窗口大小就很大了
根据数据内容,校验传输过来的数据和接收到的数据是否一致,当校验和不同,重新发送
(校验方法同UDP类似,具体情况看开头给出的详解UDP链接)
标识哪部分数据是紧急数据
暂时忽略
确认应答机制,是TCP保证“可靠性”最核心的机制
(TCP可靠性的核心,没有之一)
客户端给服务器发送数据时,给字节数据编号,形成一份普通报文,普通报文就有序列号和长度
当服务器收到数据后会返回应答报文表示数据已经收到,应答报文就包括了确认序列号,就证明确认序列号之前的数据我服务器都收到了,接下来你从这个确认序列号开始发数据
问:从图中的体现可以看出,序列号和确认序列号非常重要,那么到底是为什么呢?
①避免“后发先至”的问题:即当主机A向主机B连续发送多条数据的时候,就会产生先发的数据和后发的数据,我们都知道主机A到主机B的网络路径有很多条,因此,两个数据(两个包)从A到B的路线也不一定相同,除此之外,每个路径的节点(路由器/交换机)繁忙程度也不一样,就会导致发送数据所需时间不同,可能就造成后发的数据会先到,先发的数据后到这种情况,而这种情况,可能有很大的影响
②对字节数据进行编号之后,就算存在“后发先至”的情况,后发的数据即使先到了,也没关系,因为TCP会在内核中给每个Socket对象(网卡文件)都安排一个内存空间,相当于一个队列,也称为“接收缓冲区”,将收到的数据都放进这个“接收缓冲区”,并且按照序号排好序,此时每个数据都是有序且有编号了,因为编号也是按有序排列的,然后根据对应编号发送对应的数据即可
超时重传机制,是TCP“可靠性”机制的有效补充
①丢包:表示在网络上发一个数据,然后数据丢失了
②原因:网络传输数据时,有些网络设备太繁忙了,就会让后面新来的数据等太久,导致后面新来的数据被丢弃
(网络负载越高,越繁忙,丢包率越高)
情况一(发送数据本身丢包):当客户端给服务器发数据时,此时如果出现丢包,即数据丢失,服务器收不到数据,就会导致客户端收不到应答,等待一定时间后,客户端就会尝试重新传输数据
情况二(应答报文丢包):当客户端给服务器发数据时,此时没有出现丢包,数据成功的发给了服务器,但是,服务器在发应答报文的时候丢包了,这个时候客户端就收不到应答,等待一定时间后,客户端就会尝试重新传输数据
⭐无论是情况一还是情况二,都是由发送方重传
这里的重传是需要等待一定的时间,超过一定的等待时间后再进行重传,这就叫超时重传
①重传大多数时候数据都能传过去
原因:因为丢包是个“概率性”问题,随着重传次数越来越多,总体来说数据能传输成功的概率是更高的,比如丢包概率是10%,连续两次丢包的概率就是1%;当然也会存在连续重传多次后依然丢包,这种情况就是此时丢包率达到100%,可能是网线断了
②随着重传次数的增加,等待时间也会越来越长,直至重传次数到达一定程度后不再等待
原因:正常情况下,第二次或第三次重传数据成功的概率是极高的,如果尝试几次都没成功,就说明网络本身丢包的概率是极大的,可能是网络故障了,就算再频繁的传输也是白费力气,此时就会拉长时间间隔再轮转几次,当然,重传次数也是有一定程度的,超过这个程度后就会尝试重置TCP连接,这里就涉及到了RST,表示要求重新建立连接的复位报文段,如果连重置操作也失败,就只能放弃了,把对端的所有信息都删除
③超时时间不是一个固定的值,它会随着超时轮次的增加而增加
(这里的时间数值都是可变且可配置的,不用记也行)
④重传数据,那么TCP协议需要能够识别出那些包是重复的包,并且把重复的数据包丢弃掉,这就叫去重
如何去重:使用TCP序号来作为判断依据;TCP会在内核中给每个Socket对象(网卡文件)都安排一个内存空间,相当于一个队列,也称为“接收缓冲区”,将收到的数据都放进这个“接收缓冲区”,并且按照序号排好序,如果序号一样那就是重复,反之也没有重复
⑤我们说接收缓冲区是一个队列嘛,当数据被读取后,数据就不在队列里了
接收缓冲区:接收缓冲区其实也是一个生产者消费者模型
(1)当接收方收到数据后,接收方的网卡会把数据放到对应socket的接收缓冲区中
(2)应用程序,调用inputStream.read,就是从这个接收缓冲区中读取数据,当数据被read按编号顺序读取后,有两种方式,默认情况是read读到就删除,另一种情况是read读到不删除
⑥既然接收缓冲区中读取数据后数据被删除,那如果接收方读取了数据,发送方又继续重传,那如何去重呢?
还是序号判断;如果序号已经被读走了,说明此时重传的数据肯定是重复的
我们需要知道,接收缓冲区的序号都是有序排列的,如果此时接收缓冲区队列的首元素序号已经超过了重传数据的序号,就说明这个重传数据是重复的是被读过的了
⑦后发先至问题,如果先发的数据丢包了,后发的数据先到了,接收方不会立即读取后发的数据,而是一直阻塞等待前面的数据重传到了后,全部数据在接收缓冲区再排好序进行真正的read读取
连接管理机制,也对TCP“可靠性”起决定作用的机制
面试常考
(1)建立连接:三次握手
(必须是客户端主动发起)
(2)断开连接:四次挥手
(客户端和服务器都能主动发起,大部分还是客户端发起)
①握手:即handshake;一次握手即发一个“打招呼”的数据
(这个“打招呼”的数据并不是真正有意义的,也不携带业务信息,只是打个招呼表示要建立连接而已)
②三次握手:主机A和主机B要想完成建立连接过程,就需要三次这样的"打招呼"的数据交互
(三次交互完毕之后,连接就建立完成,此时通信双方在各自内存就保存对端信息)
③详细完整的三次握手图:
④重点掌握图:
面试中,如果HR问你TCP三次握手的过程是怎么样的?
先画图,然后按照过程简述一下,这道面试题就基本过关!
⑤三次握手的含义与功能:
(1)验证网络通信是否顺畅;以及验证每个主机的发送能力和接收能力是否正常
(2)协商必要参数的类型和数值;使客户端和服务器使用相同的参数进行消息传输
⑥为什么必须是三次握手,两次可不可以呢?
不行!因为服务器这边漏了验证信息!
举例如下图所示:
①四次挥手:主机A和主机B要想完成断开连接过程,就需要四个步骤
(经过四个步骤后,连接就彻底不再使用,双方就可以把存储对端信息的内存释放了)
②详细完整的四次挥手图:
③重点掌握图:
④问:如果服务器在发完ACK之后一直不释放也就是不断开连接会怎么样?
答:客户端若一直没有收到服务器发的FIN,就会单方面放弃连接,客户端就会把自己保存的服务器信息给删除即释放了
(一般来说,释放资源能双方顺利释放固然最好;假设条件不允许,单方面释放也没问题)
⑤问:如果四次挥手过程中发生丢包怎么办?
答:分三种情况
(1)如果是第一组FIN和ACK丢失,客户端直接重传FIN即可
(2)如果是第二组的FIN丢失,服务器直接重传FIN即可
(3)如果是第二组的ACK丢失,此时我们的客户端会在发出去最后一个ACK的时候,会让连接再等待2MSL时间,如果过了这个时间后,服务器没有重传FIN,就认为服务器收到了ACK
MSL:任何报文在网络上存在的最长时间;超过这个时间报文将被丢弃
(2MSL是因为假设在极端情况下,客户端用最长时间发ACK到服务器结果丢包了,服务器重传FIN给客户端也用了最长时间)
减少等待ACK的时间,批量发送数据,提高传输效率
窗口大小指的是无需等待确认ACK应答而可以继续发送数据的最大值
(如果发多条数据,每次都要等接收到ACK后再发一条,就会使效率降低很多)
①一般来说,窗口越大,代表批量发送的数据越多,效率就越高
②窗口不能无限大
假设你无限大,接收方不一定能处理过来,反而会出现丢包重传情况而降低效率
实际的窗口大小是比较阻塞窗口和流量控制窗口的值,采用更小的值
(窗口大小主要靠流量控制机制和拥塞控制机制决定)
①一次性发出一组数据,发出第一组时无需等待ACK直接全部往服务器发
②收到第一个ACK后,滑动窗口向后移动,继续发送第下一个段的数据;依次类推
(即每收到一个ACK,就发送下一条数据,直至全部发送)
情况一:ACK丢了
解决:不做任何处理;部分 ACK 丢了并不要紧,因为可以通过后续的 ACK 进行确认
(一般来说,丢一部分ACK不影响可靠传输,除非是全部ACK丢了,但这个时候就属于严重的网络故障了)
情况二:数据包本身丢了
解决:快重传
快重传
①当发送端发的某一条数据丢失了,发送端会一直收到服务器在上一条数据返回的ACK,意思就好像在说我要的是从上一条ACK确认序号为开头的数据
②如果发送端连续收到多次服务器在上一条数据返回的ACK,就会将丢失的数据重传
滑动窗口机制,其实也不是说使用TCP就一定会涉及
①如果通信双方传输数据规模大,那肯定要滑动窗口,如果数据丢包就按"快重传"处理
②如果通信双方传输数据规模小,就不会用到滑动窗口,如果数据丢包就按"超时重传"即可
根据接收方处理能力,来限制发送方窗口大小的机制
(简单来说就是给滑动窗口刹车,避免窗口过于无限大,导致接收方处理不过来)
(流量控制和拥塞控制都是对滑动窗口的保护,共同决定发送方的窗口大小是多少)
(流量控制考虑的是接收方的处理能力;拥塞控制描述的是传输过程中中间节点的处理能力)
将接收缓冲区剩余空间大小作为衡量标准
(剩余空间越大,应用程序消费数据就越快)
接收方将接收缓冲区剩余空间大小通过ACK报文反馈给发送方,作为发送方下一次发送数据窗口大小的依据
TCP报头里面就有个16位的窗口大小用来表示接收接收缓冲区剩余空间大小
①每次接收方接收完数据,发送的ACK除了确认序号,还将带有接收缓冲区剩余空间大小
(接收方将自己可以接收的缓冲区大小放入 TCP 报头中的 "窗口大小" 字段)
②接收方一旦发现自己的缓冲区快满了,就将窗口大小设置成一个更小的值通知给发送方
发送方接受到这个窗口之后,就会减慢自己的发送速度
③如果接收方缓冲区满了,就会将窗口置为0;这时发送方不再发送数据
但是发送方需要定期即周期性的发送一个窗口探测包,使接收方把窗口大小告诉发送方
如果窗口探测后还是0则继续等,不是0就按照返回的接收缓冲区剩余空间发送
(这个窗口探测包,不携带任何数据,只是为了查询当前接收缓冲区的情况)
将传输两端的中间所有节点看成一个整体,动态调整产生一个合适的窗口大小
(1)使用一个较小的窗口传输,如果传输通畅,就调大窗口
(2)使用一个较大的窗口传输,如果传输丢包或者阻塞拥堵,就调小窗口
(流量控制和拥塞控制都是对滑动窗口的保护,共同决定发送方的窗口大小是多少)
(流量控制考虑的是接收方的处理能力;拥塞控制描述的是传输过程中中间节点的处理能力)
在拥塞控制机制下,采用的窗口大小
(1)发送开始的时候,定义拥塞窗口大小为1
(2)每次收到一个ACK应答,拥塞窗口加1(3)每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口
①先进行慢启动,给一个小窗口,避免本来就是网络拥堵,突然又传输一个很大的流量使得雪上加霜,然后定义一个慢启动阈值
(当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长)
慢启动:刚开始进行通信时给个小窗口,探探路,先摸清当前的网络拥堵状态,再决定按照多大的速度传输数据
②然后呈指数增长状态(*n),一直增长到拥塞窗口为慢启动阈值后停止指数增长
指数增长:增长速度极快,因此必须得加以限制,否则会出现很大的值,上述的慢启动阈值就是一个限制
③停止指数增长后,此时进行线性增长状态(+n)
线性增长:同样也是增长,不断使网络传输速度加快
④当拥塞窗口到达一定的的极限后会出现丢包,如果丢包了就认为当前网络出现拥堵,就会把拥塞窗口转换为最开始的小窗口,重复上述指数增长➜线性增长状态
(阈值大小也会重新进行调整,新的阈值=丢包时的拥塞窗口大小 / 2)
利用谈恋爱的“热恋期”来理解
本来两个人不太熟悉所以慢慢来,然后经过一段热恋期(指数增长),热恋期后逐渐归于平淡(线性增长),当到了一定程度后爆发矛盾想分手(回到最初)
提高效率的机制,也是建立在滑动窗口的基础之上
(主要目的在于使窗口变大)
收到数据后,不是立即就返回 ACK ,而是拖延一点时间,利用这个时间给应用程序更多消费数据的时间,此时就会使接收缓冲区剩余的空间更大了,则下一次的窗口就会更大一点,我们都知道窗口越大,数据传输就更多,效率就更快
延迟时间为多少进行应答?
滑动窗口:进行数量限制;每隔N个包就应答一次
非滑动窗口:超过最大延迟时间就应答一次
(具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms)
假设滑动窗口下每隔2个包就应答一次,其实ACK也不是每一条都会返回,只要有部分ACK返回即可,因为上文已经说过了ACK的确认序号代表这个确认序号之前的数据接收方都已经收到了,即使1001ACK没收到,只要收到2001ACK,就代表1-2001的数据都收到了
一种提高效率的方式,在延时应答的基础上,加上捎带应答
A对B发请求,B把ACK和响应一起传过去
字节数据粘在一起,无法确定一个完整的应用层数据报头和尾是哪里
①接收方收到的字节数据都会放到接收缓冲区里
②那么当应用程序 read 读取缓冲区里数据时,一次读取N个字节,这是可以通过代码设置的
③一次读N个字节导致可能一次读到的数据是半个应用层数据报,也可能是一个应用层数据报,还可能是多个应用层数据报,就不知道从哪开始到哪结束算一个完整的应用层数据报
①应用层协议中,引入分隔符(类似于“\n”),区分包之间的边界
②应用层协议中,引入包长度,区分包之间的边界
(约定每个应用层数据包的前N个字节来存储数据包的长度,读到长度后根据长度读后续数据)
例如:约定这个应用层数据包的前4个字节来存储数据包的长度,假设数据为“6hlizoo”, 应用程序会先读前4个字节读到了6 , 就知道继续往后读6个字节就是一个完整的应用层数据包
传输过程中出现了一些异常因素使得TCP连接无法正常工作
①现象:进程奔溃就是相当于进程没了,停止工作了,此时PCB就没了,那么文件描述符表也被释放了,这个过程就相当于调用了socket.close();进程奔溃的一方就会发出FIN,进一步的触发四次挥手,其实就是一个正常断开的流程
②处理:此时TCP的处理就跟正常断开连接没什么区别
①现象:正常关机,就是强制关闭了所有正在执行的进程,然后才正式关机,这里的强制关闭所有进程跟上述进程奔溃是一样的,也会触发四次挥手,也是一个正常断开的流程
②处理:此时TCP的处理就跟正常断开连接没什么区别
这种情况下,很显然是来不及进行四次挥手的
①现象一:接收方掉电
处理:如果发送方一直收不到接收方的ACK,那么此时就会触发超时重传机制,重传次数达到一定程度后就会尝试重置TCP连接,若重置TCP连接还是失败,此时发送方就单方面释放放弃连接
②现象二:发送方掉电
处理:接收方通过心跳包来发现连接异常来放弃连接
(心跳包:接收方周期性的给发送方发送一条消息,这条消息不携带任何业务数据,目的只是在于触发一下ACK,来判断发送方是否还正常工作或者网络顺畅,如果发送方不能返回ACK则说明发送方挂了;类似于流量控制机制的窗口探测包)
这种情况下,很显然是来不及进行四次挥手的
分两种情况,情况和主机掉电一样,这里就不再赘述
①TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景
②UDP用于对高速传输和实时性要求较高的通信领域,例如,早期的QQ,视频传输等
另外UDP可以用于广播
归根结底,TCP和UDP都是程序员的工具,什么时机用,具体怎么用,还是要根据具体的需求场景去判定