设计一个应用层协议,主要就是包含两个工作:
当下比较流行的一些协议的模板(数据的组织格式)
xml: 可读性好,但是运行效率不高
是属于一种比较老牌的数据格式了,现在虽然也在用,但是用的越来越少了;xml的格式非常有特点,它是由标签构成的⬇️⬇️⬇️
json: 当下最流行的一种,但是和xml一样,可读性好,运行效率不高
注意:
json要求key一定是字符串,因此key这里的引号可以省略,除非key中包含了一特殊符号(比如像空格或者-…)此时就必须要加上引号了。
protobuffer: 可读性不好,运行效率高
由于当用json表示一个更加复杂的数据,比如数组的时候,此时这里的很多key就会重复出现N次,也就占用了更多的额外带宽。
于是protobuffer的产生就很好的解决了这个问题,protobuffer是一种二进制格式的数据,在protobuffer的数据中,不再包含上面key的名字了,而是通过顺序以及一些特殊符号来区分每个字段的含义,同时再通过一个IDL文件来描述这个数据格式(每个部分是啥意思),IDL只是起到一个辅助开发的效果,并不会真正的进行传输,传输的只是二进制的纯粹的数据。
HTTP: 当前最知名的应用层协议
负责数据能够从发送端传输到接收端。
UDP协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。
TCP,即Transmission Control Protocol,传输控制协议。人如其名,要对数据的传输进行一个详细的控制。
TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率。
这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。
确认应答: 保证可靠传输的核心机制,关键是接收方收到消息之后,给发送方返回一个应答报文(ACK,acknowledge),表示自己已经收到了。
可靠性: 发送方发出去数据之后,能够知道对方有没有收到
上面这个确认应答虽然汤姆收到了杰瑞的回复,但是还是有一点问题的,就是如果汤姆一下子给杰瑞发了好几条消息呢,会不会收到相对应的回复?
按理说正常情况下应该是上面这个样子,但是网络上,数据接收的顺序,不一定和发送的顺序完全一致❗存在后发先至的情况❗❗❗
从上面的图可以看出不可能!!!明明是后发的,但是反而先到了;这是因为网络环境非常复杂,连续发的两个包,不一定就是走同一条路。
那么该如何解决上述问题呢❓那就是对消息进行编号⬇️⬇️
序号和确认序号:
确认序号是如何进行应答的:
相当于对确认应答进行了补充,确认应答是网络一切正常的时候,通过ACK通知发送方我收到了,如果出现了丢包的情况,超时重传机制就会起到效果了
出现丢包的两种情况:
出现丢包情况时,如果是ACK丢了,那么此时触发了超时重传,就会导致接收方收到了重复的消息❗❗
而此时TCP内部就会有一个去重操作,接收方收到的数据会先放到操作系统内核的“接收缓冲区"中,接收缓冲区可以视为是一个内存空间,并且也可以视为是一个阻塞队列;
收到新的数据后,TCP就会根据序号来检查看这个数据是不是在接收缓冲区中已经存在了;如果不存在,就放进去,如果存在,直接丢弃。
保证应用程序调用socket api 拿到的这个数据一定是不重复的❗❗❗
基于上述确认应答(ACK)和超时重传两个机制,TCP的可靠性得到了有效的保障❗❗
三次握手意思是客户端和服务器之间,通过三次交互,完成了建立连接的过程,"握手"只是一个形象的比喻
三次握手相当于“投石问路”,检查当前这个网络的情况是否满足可靠传输的基本条件(如果你网络本身就效果非常差,那么强制进行TCP传输,也会涉及到大量丢包)
更具体的说,可以认为三次握手其实也是在检测通信双方的发送能力和接受能力是否都正常
比如:当本博主和小姐妹在使用QQ进行语音聊天时
上面已经详细的给大家介绍了TCP三次握手的过程,这里不再详细说了。(小tip:以后面试的时候最好给面试官画图!!)
四次握手: 行,但是没必要,分开传输降低效率,不如合在一起;
两次握手: 不行,意味着缺少最后一次,此时客户端这边关于发送接收能力正常的情报是完整的,但是服务器这边是残缺的;
服务器不知道自己的发送能力是否ok,也不知道客户端的接受能力是否ok,此时此刻,服务器相对于当下能否可靠传输,心里是没底的,这第三次交互,就是为了给服务器吃一个定心丸。
滑动窗口存在的意义就是在保证可靠性的前提下,尽量提高传输效率
因此就引入了滑动窗口机制,而滑动窗口本质上就是在“批量的发送数据”,一次发一波数据,然后一起等一波ACK,这样就降低了等待时间,提高了数据传输效率。
虽说使用滑动窗口机制可以提高传输效率,但是还有一个核心问题就是:在这个背景下,如果丢包,那么该如何进行重传呢❓❓❓
丢包分成两种情况:ACK丢了或数据丢了
所以在这种情况下, 如果部分ACK丢了并不要紧也不用管,因为可以通过后续的ACK进行确认
这里的重传只是需要把丢了的那一块数据给重传了即可,而其他已经到了的数据就不必再重传了,整体的重传效率还是比较高的,因此我们把这个过程称为快重传
这个快重传就像我们小时候看电视剧一样:
由于我们小时候(仅代表博主本人)网络还不是那么发达,所以我们只能通过电视来看自己想看的剧,而电视呢每天都是固定时间播放的;而如果有天你有事情,导致你有一集没能看成,那你就只能继续先往后看;而某天你突然发现另外一个电视台也正在播这个剧,那么你就可以通过这个台把落下的那集补上即可。
虽然在滑动窗口中,窗口越大,传输速率就越高,但是我们不能光考虑发送方,我们还要考虑接收方;
接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制(是滑动窗口的延申,目的是为了保证可靠性——能够衡量接收方的处理速度)
这就像我们小学的时候做的某道数学题——有个水池,一边往里面流水一边往外面放水:此时进水的速度就取决于当前水位的高度;水位高(说明剩余空间小了),进水就慢点;水位低(说明剩余空间大了),进水就快点。就直接用剩余空间的大小来制约进水的速度,当然剩余空间的大小,也是在侧面描述出水的速度。
那么接收方如何告知发送方,剩余空间有多大呢?
通过ACK报文来告知——回忆我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息;
过程如下:
拥塞控制也是滑动窗口的延伸,也是限制滑动窗口发送的速率
拥塞控制衡量的是:发送方到接收方,这整个链路之间,拥堵情况(处理能力)
相当于流量控制的延伸
流量控制是踩了下刹车,使发送方发的不要太快;
延时应答就想在这个基础上,能够尽量的再让窗口更大一些
客户端和服务器之间的通信有以下几种模型:
不仅仅是TCP存在粘包,其他的面向字节流的机制也存在,比如读文件;
TCP粘包指的是粘的是应用层数据报,在 TCP 接收缓冲区中,若干个 应用层数据包混在一起了,分不出来谁是谁。
解决方案:关键就是要在应用层协议这里,加入包之间的边界(例如:约定每个包以 ; 结尾)⬇️⬇️⬇️
就是在进程毫无防备的情况下,偷袭他,突然结束进程,那么这个时候该进程的TCP连接是怎么样的呢❓❓❓
TCP连接是通过socket来建立的, socket本质上是进程打开的一个文件,文件其实就存在于进程的PCB里面有个文件描述符表,每次打开一个文件(包括socket),都在文件描述符表里增加一项;每次关闭一个文件,都在文件描述符表里,进行删除一项。
如果直接杀死进程,PCB也就没了,里面的文件描述符表也就没了。此处的文件相当于“自动关闭”了,这个过程其实和手动调用socket.close() 一样,都会触发4次挥手
按照操作系统约定的正常流程关机,而正常流程的关机,会让操作系统杀死所有进程,然后再关机,这也就相当于又回到了我们的第一个异常情况。
啥时候使用TCP❓
对可靠性有一定要求时优先使用TCP(日常开发中的大多数情况,都是基于TCP)
啥时候使用UDP❓
对可靠性要求不高,对于效率的要求更高时使用UDP,因为毕竟TCP的可靠性是建立在效率的折扣之上的。
一个典型的例子就是: 机房内部的主机之间通信,尤其是这种分布式系统中—广播就是首选UDP
这个题看似是在考查UDP,其实就是在考TCP,这翻译过来就是三个字:“ 抄作业”,TCP是怎么实现可靠性的,那就在应用层去复刻一下,本质上就是在应用层基于UDP复刻TCP的机制