目录
TCP/IP协议
应用层
传输层
UDP
TCP
TCP的核心机制
①确认应答机制
②超时重传
③连接管理
④滑动窗口
⑤流量控制
⑥拥塞控制
⑦延时应答
⑧捎带应答
⑨面向字节流(粘包问题)
⑩TCP的异常处理
几个问题:
这里的TCP/IP协议重点介绍的是TCP/IP五层协议栈,也就是从五层协议来分析,主要介绍前四层,而最后一层物理层就不过多介绍了,这一层主要还是偏硬件方面,因此呢就主要介绍一下前面四层和软件相关的协议!!
这一层对于程序员来说是最为重要的一层,这也是程序员打交道最多的一层,其他四层基本都由操作系统,驱动,硬件实现好了,而在应用层需要我们自己"设计并实现应用层协议"
简单举个例子来描述一下怎么实现应用层协议:
假如公司现在在开发一个项目:点外卖的软件,当前要开发一个功能:获取用户的历史订单(这个订单是在数据库中的,需要服务器拿),那么这个功能就需要涉及到前端(客户端)和后端(服务器)彼此之间的交互才能完成的,而前端和后端就是通过网络来进行交互的,在这个交互的过程中,就需要提前约定好,前端要发送什么样的数据,后端要回应啥样的数据!
比如前端发送的请求就要包括:用户的id,查询的起始时间,查询的结束时间,显示的条数
后端回来的响应(数据):查询是否成功,如果失败失败的原因是什么,结果数组(商品名,商品单价,商品数量,店铺名称,总金额)
而形如上面前端的请求和后端返回的响应这样的工作就是在设计一个应用层协议,而这里不仅要约定好传输的数据,而且要约定一个具体的格式,方便查看识别;
因此呢要设计一个应用层协议就需要包含两个工作:
1.明确要传输的信息(根据需求)2.明确数据的组织格式
而对于这样的组织格式,为了保证协议都设计的不会太差,因此一些大佬就发明了一些比较好的协议模式(数据的组织格式),来供我们使用,而当下比较流行的一些组织格式有:xml(格式:<标签名>内容标签名>),json({键:值,键:值.....}),protobuffer(是一种二进制格式的数据,会通过顺序以及一些特殊符号来区分每个字段的含义,同时再通过一个IDL文件来描述数据格式,IDL不会真正传输,只是辅助的作用,传输的还是二进制的数据)
前两种都是可读性好但运行效率不高,protobuffer运行效率高,但可读性不好,最常使用json来组织数据格式!
另外也不是所有的应用层协议都需要自己设计,还有一些已经设计好的一些应用层协议,只要稍微进行一点二次开发就可以使用了,最常见的一个就是HTTP协议,还有其他类似DNS协议,NAT协议等等都可以直接使用...
传输层是操作系统内核实现的,而且只要进行网络编程的话就需要使用到socket,一旦使用了socket代码就一定会进入到传输层的范畴中,因此传输层非常非常重要的,尤其是传输层的UDP协议和TCP协议!
学习一个协议,很多时候就是在研究报文格式,因此我们来研究一下这两个协议的报文格式~
UDP
但是这样的拆包装包过程是非常复杂的,所有这样的方式数据下下策,那么有什么上策吗?当然有那就是不使用UDP,转而使用TCP ,因此下面介绍一下TCP
TCP
TCP的源端口号和目的端口号和UDP的是一样的;
4位首部长度:TCP报头的长度,单位为字节(TCP的报头是变长的,不像UDP是固定产长度),要变长的话可以在下面的选项里面添加一项选项;
保留位:表示当前可能没有用到,但是后续升级TCP的话,是有可能用到的,需要留空间;
校验和:检验传输的数据是否正确;
选项:可以没有,也可以有多个;
剩下的目前还不曾了解,下面会通过TCP的十个核心机制介绍!
①确认应答机制
这个机制是保证可靠传输的核心机制,对于TCP而言可靠传输是非常重要的,而可靠的传输就是发出数据之后,能够知道对方到底有没有接收到数据(也就是对方在收到发的数据之后,会给发送方返回一个应答报文(ACK,TCP的核心字段之一),表示收到了发送方的消息)
简单举个例子
但是如果发送多条信息的话,会发生什么情况呢?
但是如果出现了丢包的问题(消息根本没有发送出去),这种情况有该怎么解决呢?下个机制就可以用来解决这个问题
②超时重传
这个机制是对确认应答进行了补充,如果网络出现丢包问题,就应该使用超时重传机制了
如果网络出现一点问题就会有可能产生丢包问题!那么该如何解决呢
这样就很大概率解决了丢包问题,但是如果出现了重大的网络问题,重传多少次都不会收到ACK的,因此这里的重传也不是无休止的重传,如果连续重传好几次都不能收到ACK的话(本来丢包的概率就是很小的,连续丢了好几次的概率就更小更小了),就会认为这个网络是出现了重大的网络问题,就会自动放弃TCP的连接,而且这里重传的时间间隔也不是一成不变的,这个会随着重传次数的增加而越来越长的,当然正常的网络问题超时重传就可以解决了,但是重大的网络问题,这都是没有办法的!
③连接管理
这个机制也是保证可靠性的机制,主要介绍TCP是如何建立和断开连接的(三次握手,四次挥手)
1)如何建立连接(三次握手)
所谓三次握手其实就是客户端和服务器之间的三次交互过程,通过这个过程来建立连接
这里面还有两个相对关键的状态(了解):
LISTEN:表示服务器启动成功,端口号绑定成功,随时可以和客户端建立连接
ESTABLISHED:表示客户端已经建立连接了,随时可以进行通信
还有一个比较关键的点就是三次握手有什么用?和可靠性有什么关系?
·三次握手,相当于是一个"投石问路"的过程,检查一下当前网路是否满足可靠传输的基本条件,也就是在检验通信双方的发送能力和接收能力都是否正常,如果都正常的话,就可以来建立连接,而如果不正常就会通过上述的确认应答和超时重传来看是否正常,如果重传好几次都发现没有回应,那么就证明出现问题,也就不能建立连接了,不满足可靠传输的基本条件,就会放弃本次连接,这样通过三次握手就可以保证可靠性;
·让通信双方能协商一些必要的信息(确认一些重要的参数),在TCP通信中,是需要客户端和服务器有一些共同信息的,因此在三次握手的同时也会交互一些必要信息,类似于确定好吃饭的时间地点......
这里还要几个经典的面试题:
*描述三次握手的过程(简略图):
*为啥握手是三次?两次行不行?四次行不行?
通过上面,我们也都知道了四次是可以的,但是没有必要的,四次效率会低一些,一般都是把中间的SYN和ACK合二为一的,而两次是绝对不行的,两次只能确定双方中一方的发送和接收能力正常,另一方就不清楚了,这是不满足可靠传输的基本条件的,因此是不可以的,三次是最好的!
2)如何断开连接(四次挥手)
上面的三次握手已经建立好了连接,而建立了连接之后操作系统内核就需要使用一定的数据结构来保存建立连接相关的信息(客户端和服务器都要保存五元组,这就需要占用系统资源(内存)),而想要断开连接的,就需要释放这些资源,而四次挥手就是做这样的事,虽然三次握手和四次挥手的目的不一样,但是基本流程都是差不多的
两个相对关键的状态:
CLOSE_WAIT:四次挥手执行了两次之后出现的状态,这个状态就是在等待代码中调用socket.close()方法,来方便完成后续的挥手过程,如果一个代码过程中存在很多的这种状态表示这个代码是存在bug的,执行不到close语句,就需要检查代码了!
TIME_WAIT:哪一方主动发起的FIN,就会在发送玩ACK之后进入这个状态,表示给自己发送的这次ACK提供一次重传机会,比如下面情况:
这样才能真正完成四次挥手的过程!
④滑动窗口
TCP不仅仅是保证可靠传输的,在这个基础上也是要尽量提高效率的,而滑动窗口就是尽可能提高效率的,
假如不使用滑动窗口的话,每发送一组数据都会等待一个ACK,这样的话,就会把大量的时间花费在等待ACK上,这是不值当的,效率也比较低,因此TCP就有一个机制解决了这样的问题,就不再是一次发送一组数据了,而是一次发送一批数据(不止一组):
就会像这样一样一次发送一批数据(1~4000,算是一次发送了4组数据,等这些都发送完了再等待ACk),等收到1001后表示收到了1~1000,然后就发送4001~5000这组数据,这样就不需要等待每一个ACK的到达之后才能发送,然后2001到了之后就发送5001~6000,依次这样下去,相当于一份等待时间等待了多份ACK,因此时间就会得到提升
这就类似于一个滑动窗口一样,而且每个窗口要等待的ACK的范围也是不一样的,而且当这个窗口越来越大的话,传输效率也会越来越快,窗口大了相当于同一份时间内等待的ACK就越多,总的等待ACK的时间就会更短,但是也不是窗口越大越好,窗口的大小也不是随便定义的,下面的机制会详细介绍
而滑动窗口的批量发送数据,就会产生一个非常重要的问题:丢包问题,丢包了该怎么办呢,下面来介绍一下:
一般丢包有两种情况:
☆ACK丢了:
因此这里不需要做出任何的处理,就可以解决ACK丢包的问题了
☆数据丢了
因此在B不断的"索要"下,A触发重传,数据丢包的问题也就解决了,哪里缺省了就会索要哪里,而后面收到的就不需要再重传了,因此这个重传也是很快的,因此整体的重传效率也会非常高,也不会因为这个降低滑动窗口的效率(快重传)!!
⑤流量控制
这个机制是在滑动窗口的延伸,进一步保证了可靠性问题,也就是上面的窗口大小问题,滑动窗口的大小不仅要考虑到发送方,还要考虑到接收方,如果发送的太快,接收方处理不过来,导致丢包了,那还是得重新发送这就不值当了,那么流量控制的关键就是衡量接收方的处理能力,此时就用接收方的接收缓冲区的大小,来表示当前的处理能力
随着接收缓冲区的空间越来越小,发送方就会发送的慢一点,也可以认为空间比较大的话,表示B的处理能力很强,就可以让A发送的快一些,剩余空间比较小的话,B的处理能力就相对慢一点,就可以让A发送的慢一点,那么这个空间的大小如何能让A知道呢?其实在ACk里面就包含了这些信息,在TCP的报头中有一条这个16位窗口大小就可以衡量接收方剩余空间的大小,发送方收到这个数据后,就会根据这个大小来调整发送速率的快慢,而且这个16位不一定只能存放64k,实际还可以更大,在TCP的选项中还包含了一个窗口扩大印子M,通过这个就可以调整窗口的大小.
另外当剩余空间被占满的时候,A就不会在发送数据,但也不是永远都不再发送数据了,而是会在一段时间后发送一个窗口探测,这个窗口探测不会发送任何数据,只是为了探测窗口的大小,来确定自己要不要再发送数据,例如下面的情况:
这样通过流量控制就可以一定程度上保证滑动窗口的可靠性了!
⑥拥塞控制
也是滑动窗口的延伸,也是限制滑动窗口的速率,其实滑动窗口的大小不仅取决于接收方的处理能力,而且在发送方和接收方的中间传输存在一些链路的,而这些链路是否拥堵也是可以决定这个传输速率的,比如:
而要找出最适合的值,就需要通过不断的调整发送速度来衡量了,就比如A刚开始会以一个较小的窗口大小来传输数据,如果很快的就到达了,就逐渐加大传输速率,直至出现丢包问题,就表明当前链路出现问题了,就降低传输速率,就是通过这样不断的增加和减少传输速率来测量出合适的范围~然后拥塞窗口的大小就会出处在这个范围内,达到"动态平衡"
那具体这个拥塞窗口值是如何变化的呢?
通过这样的拥塞控制和上面的流量控制就会保证滑动窗口的速率,而且可靠,而且如果这个窗口的值会取到拥塞控制和流量控制的最小值,就是两个控制都得保证,这样才可以更可靠,更快!
⑦延时应答
流量控制的延伸,流量控制是让发送方发送的不要太快,而延时应答就是在这个的基础上,尽量扩大滑动窗口的大小
而通过这样的延时应答也会尽可能的提高一点窗口大小,来提高传输速率,而延时应答的时间也是有考究的,数量上和时间上都会加以限制,数量:每隔N个包就应答一次,时间:超过最大的延迟时间就应答一次,因此通过这样的方式也是提高了一些传输速率!!
⑧捎带应答
这个机制又是延时应答的延伸,客户端和服务器之间的通信有一下几种模型:
●一问一答:客户端发送一个请求,服务器回复一个请求
●一问多答:下载文件
●多问一答:上传文件
●多问多答:直播...串流...
而一问一答是网络中最常使用的:
而通过这样的捎带应答也是可以提高一些传输效率的!!
⑨面向字节流(粘包问题)
不仅仅TCP有粘包问题其他的面向字节流的机制,也是存在的,比如读文件;而这里的TCP粘包问题指的是应用层数据报,在TCP的接收缓冲区中若干个应用层数据报就会粘在一起,分不清哪个是哪个,比如下面的情况:
而B的应用程序如果通过read方法从接收缓冲区中取数据的时候,因为TCP是面向字节流的,因此就会把这些数据全部取出来,而取出来之后就无法区分哪个数据报是哪个数据报了,因此放在接收缓冲区中的所有应用层数据报就会混在一起,这也就是粘包问题,而想要解决这个问题其实也很简单:就是在应用层数据报这里加上一些边界,能够把这些数据报区分开就行,例如可以给每个应用层数据包后面加个;来作为边界
这样也就能将这些应用层数据报给分开了,其他方法也可以,只要能主要到这个应用层的粘包问题,并且能够将应用层数据报给分割出来就是可以的,因此这个粘包问题也就解决了!!
⑩TCP的异常处理
◆进程终止
这个是在进程毫无防备的情况下,突然关闭进程,就类似于在任务管理器中直接关闭进程~
而TCP的连接是通过socket这个api来建立连接的socket本质上是打开一个文件,文件其实就存在于进程的PCB中的文件描述符表,每打开一个文件,文件描述符表就会增加一项,每关闭一个文件,文件描述符表就会删除一项,而此时直接杀死进程,PCB就没了,相应的里面的文件描述符表也就没了,就相当于文件自动关闭了,而这其实和手动调用socket.close()一样,也会触发四次挥手~因此这样就和正常关闭是一样的
◆机器关机
这里的机器关机指的是通过正常流程来关闭机器,这样就会让操作系统杀死所有进程,从而关闭机器,这也就和上面的手动调用close方法是一样的,也是正常的
◆机器掉电/网线断开
这就类似于台式电脑直接拔掉电源,此时操作系统是不会有任何反应时间的,更不会有任何的处理措施,有以下两种情况:
几个问题:
♣TCP和UDP的对比:
啥时候使用TCP? 对可靠性有一定要求(大部分的开发都是这样的,基于TCP来开发)
啥时候使用UDP?对可靠性要求不高,对效率要求更高一些(例如机房内的机器间通信,分布式系统中就对可靠性要求不那么高,而效率会更高一些,因此就会使用UDP)
♣基于UDP如何实现可靠传输?
其实本质上就是复刻TCP可靠传输的机制,然后自己实现保证可靠性,想要提高速率就再添加个滑动窗口.......