1 应用层
应用层是与程序员关系最密切的一层,在应用层这里了,很多时候,都是使用程序员自定义的协议,当然,也有很多现成的协议供我们使用。
“自定义协议”:
自定义一个协议,也就是自己做一个约定,即:
- 要传输什么数据
- 这个数据要按照什么样的格式传输
下面这个过程,就是一个自定义协议的过程
使用行文本形式定义的协议在开发中很少使用,下面介绍几种常用的协议:
1.1 常用协议:
1.1.1 xml
xml使用标签来组织数据,例如这样:
优势:数据的可读性更好
劣势:标签写起来非常繁琐,同时在传输过程中也会占用更多的网络带宽(占用更多资源)
(html也是使用标签来组织数据的,但是html的标签是固定的,不能用户自己定义)
1.1.2 json(当下最流行的组织形式)
json是以键值对的形式组织数据的,例如这样:
键值对结构:
使用{}将所有的键值对包裹起来,键和键之间,使用“,”分割;键和值之间,使用“:”分割;键固定是字符串形式,而值可以是数字,也可以是字符串,json,数组.....
优势:可读性较好,比xml更加简洁
劣势:同样会在网络传输中消耗更多的带宽(key也是需要传输的)
1.1.3 protobuffer
protobuffer是通过二进制形式组织数据的,可以保证消耗的带宽最低(相当于把数据压缩成二进制的形式了)
优势:占用带宽最低,传输效率最高,适用于需要高效率的场景
劣势:可读性不好(二进制数据,肉眼无法直接看懂),一定程度上影响开发效率(有时候,开发效率比执行效率更重要!!!)
2 传输层
传输层的协议主要有两种:
UDP:无连接、不可靠、面向数据报、全双工
TCP:面向连接、可靠传输、面向字节流、全双工
2.1 端口号
端口号指明的是我们要访问主机中的哪一个应用程序:
- 写一个服务器,必须手动指定一个端口号,让客户端知道这个服务器在哪里
- 写一个客户端,系统会自动分配一个端口号,用户是感受不到的
端口号固定就是2个字节,范围为0~65535,0通常是不用的,1~1023:“知名端口号”;1024~65535:“普通端口号”,用户在通常使用普通端口号,知名端口号被预留给一些“知名”的服务器,例如:22:ssh 80:http 443:https
接下来具体了解一下UDP和TCP的报文格式
2.2 UDP
可以看到,UDP的报头共8个字节,共分为4个部分各占2个字节,端口号前面已经介绍过,范围为0~65535,UDP的长度也为16位,表示的范围也是65535,也就是64kb,而检验和是来检验数据是否正确的(通常使用CRC循环冗余检验或者MD5算法检验)
2.2.1 UDP长度
这是我们在进行网页搜索时大致的原理图,如果采用UDP协议,由于UDP数据报的长度很短,所以如果数据超过上限,则肯定会出现一系列的问题,那么该怎么解决呢?
1、将数据拆分成多个包,使用多个数据报进行传输,但是这个方案行不通,为什么呢?因为UDP本身并没有拆包和组包的机制,如果程序员自己实现的话,会十分复杂,不可行。
2、使用TCP协议进行数据传输,可行,当我们想要传输规模很大的数据时,通常就会采用TCP数据报(下边会详细介绍)
2.2.2 检验和
检验和本质上也是一个数据,是原始数据通过一系列的操作得来的,如果原始数据相同,那么检验和一定相同;反之,如果检验和相同,原始数据也大概率相同。
如何根据检验和来判断数据是否正确呢?
- 发送方,把要发送的数据整理好(data1),然后根据data1算出自己的检验和checkdata1
- 发送方通过网络将data1和checkdata1发送给接收方
- 接收方收到数据(此时的数据可能由于传输问题已经和data1不一样了 data2)和checkdata1
- 接收方根据data2算出检验和checkdata2
- 判断checkdata1和checkdata2是否相同,如果相同,则数据正确,反之,则传输发生错误
CRC冗余检验算法这里不做介绍,重点介绍一些md5算法
2.2.3 md5
md5是用过一系列复杂的公式计算得来的,我们不需要了解,这里只需要了解md5的一些特性即可
- 定长:无论原始数据多长,md5的长度都是固定不变的
- 分散:给定的两个数据,哪怕大部分字节都相同,只有一个字节不同,得到的md5数据也是差别很大的(所以md5非常适合hash算法,冲突的概率很小)
- 不可逆:只能通过原始数据算出md5数据,但是无法将一个md5数据还原为原始数据
2.3 TCP
TCP协议最大的特点,就是可靠传输!!!(TCP协议的初心)
TCP报头的长度是不固定的(变长的),最短是20字节(没有选项),最长是60字节(选项长度最长为40字节)
TCP的相关特性:
- 有连接
- 可靠传输
- 面向字节流
- 全双工
其中,可靠传输是TCP的核心,但是,可靠传输并不是说数据能百分之百发给接收方,而是发送方在发送数据之后,能感受到数据是否成功发送给接收方,如果没有送到,则会采取一些“补救措施”再次进行发送,如果“补救措施”无效,则会放弃此次数据的发送。
接下来,具体介绍一下TCP常见的机制:
2.3.1 确认应答
发送方,把数据发送给接收方之后,如果接收方收到数据,则会返回给发送方一个应答数据(ack),发送方收到这个应答数据之后,就知道自己的数据成功发送给接收方了。但是在发送数据时可能会由于网络卡顿等原因出现“后发先至问题”,即接收数据的顺序和发送数据的顺序不一样,此时就会出现一些问题,那么该怎么解决呢?
此时就要用到TCP报头中的序号和确认序号的字段了
假设发送的第一条数据的序号为1~1000,那么接收方收到这条数据之后,返回的确认序号就应该为1001,代表之前的数据它已经收到了,接下来它想收到起始序号为1001的数据,做了这个操作之后,就不会出现上述问题了,接收方就可以准确的知道发来的数据是对于哪一条数据的应答。
TCP的核心是可靠传输,而可靠传输的核心就是确认应答
如何区分一个数据时普通数据还是ack数据呢?
常见面试题:TCP是如何完成可靠传输的?
正确答案:以确认应答为核心,通过其他机制辅助,最终实现可靠传输
错误答案:三次握手/四次挥手保证了可靠传输
2.3.2 超时重传
确认应答描述的是一个比较理想的情况,但是如果网络传输中“丢包了”该怎么办呢?数据丢了,接收方收不到数据,发送方也肯定无法收到ack了,这下双方都无法知道对方的情况了,只能陷入无限的等待,所以引入了超时重传机制(对于确认应答机制的补充)
介绍超时重传之前先要了解一个东西,为什么会丢包呢?
丢包是因为网络负荷太大,网络环境十分差,此时路由器就会随机选择一些数据包丢弃。
丢包又分为以下两种情况:
无论是上述情况的哪一种,发送方都无法知道自己的数据是否成功发给了接收方,所以在等待一段时间之后,就会进行重传(这就是超时重传)。
那么又如何确定等待的时间呢?
初始的时间是可配置的,不同的系统不一样,第一次重传就是等待初识的时间;如果第一次重传之后仍然没有收到ack,那么就会延长等待时间,继续重传;如果重传多次之后,等待时间延长到一定程度仍然没有收到ack,就会认为无论怎么发送都无法发送给对方了,此时就会放弃发送。
还有一个问题,如果接收方收到两条一样的数据(重复数据)该怎么办呢?接收or丢弃?
其实TCP早已考虑过这个问题,在TCP内部有一个接收缓冲区,接收缓冲区会保存接收到的数据以及数据的序号,如果接收的数据已经存在于缓冲区中了,那么就会直接丢弃这条数据,保证接收方不会读到重复数据。
2.3.3 连接管理(三次握手/四次挥手)
握手(建立连接)
挥手(释放连接)
握手:发送一个没有业务数据的报文,引起对方的注意,从而触发后续的操作(握手不是TCP特有的,甚至不是网络通信特有的,计算机中的很多操作,都会用到握手)
三次“握手”:通信双方在正式通信之前,共需要打三次招呼才能建立连接
A想和B建立连接,A就会主动发送请求,此时A就是客户端,而被动接受的一方,就是服务端
在经历三次握手之后,A和B互相记录了对方的信息,此时,连接建立成功(握手时发送的数据是同步报文段,是不携带任何业务数据的!!!)
建立连接时,其实是要经过四次握手的,通信双方都需要发送一次syn和ack,但是中间两次恰好可以合并成一次,所以变为了三次握手(接收方在发送ack时,可以顺便发送syn)
常考面试题:既然三次握手可行,那么四次握手是否可以呢?两次握手呢?
答:四次握手就是上面提到过的,可以但是没必要;但是两次握手是不可行的,因为双方都需要知道对方的发送能力和接收能力是完好的,如果只是两次握手,A可以知道自己的发送能力和接收能力是完好的,但是B由于没有收到A的ack,所以无法确定自己的发送能力是否完好(无法确定A是否成功收到自己的数据)
三次握手核心作用:
- 投石问路,判断当前网络是否通畅(如果没有业务数据的报文都收不到,那么正式交流的数据肯定也收不到了)
- 让双方都知道自己的发送能力和接收能力是正常的
- 让通信双方,在握手过程中,针对一些重要的参数进行协商(如发送的数据序号要从多少开始,这样做的好处是可以知道这条数据是新连接的数据还是已经断开的旧连接“姗姗来迟”的数据)
TCP各个时期还有一些对应的状态:
四次“挥手”:
建立连接一般是由客户端发起的,但是释放连接服务端和客户端都可以发起;例如生活中的男生向一个女生表白,是由男生发起的,但是分手的时候既可以男生提,也可以女生提,双方都可以提出。
四次挥手能否像三次握手那样,将中间的两步合并为一步,变为三次挥手呢?
答案是不一定,因为ACK和FIN的触发时间是不同的,ACK是由内核相应的,B收到FIN,就会立即返回ACK,但是此时B可能还不想断开连接,只有当B调用close方法时,才会发送FIN,所以二者不能合并。
那是否意味着,接收方的close没有调用,第二个FIN就一直发不出去?
是有可能的,但是发不出去也没事,如果按照正常的流程断开连接(四次挥手),好聚好散是最好的;但是如果没有挥手四次,异常的断开连接,也是存在的。
2.3.4 滑动窗口
前三个机制,都在保证TCP的可靠性,但是可靠性的提升是以降低效率为代价的,所以要想一些办法来提升效率,滑动窗口就是其中一个。
以上三张图大致讲述了滑动窗口的原理,但是此时又出现问题了,使用滑动窗口之后,丢包该怎么办呢?如何进行重传呢?
最后一个问题,滑动窗口是越大越好吗?
当然不是,虽然滑动窗口可以提高效率,但是如果窗口过大,发送的数据太多,接收方就有可以收不过来,就会出现“丢包”现象。(提升效率的前提是保证可靠性!!!)
2.3.5 流量控制
流量控制是站在接收方的角度,反向控制发送方的发送速度。
这里其实类似于“生产者 消费者”模型,接收方拥有一个接收缓冲区,接收方不断的从缓冲区里读取数据,每次接收方返回ack时,都会将这个缓冲区的大小携带在数据中,发送给发送方,而缓冲区的大小就代表了接收方的接收效率,如果缓冲区剩余空间大,意味着消费速度快,处理能力强,发送方接下来可以发的更快一点;反之,如果缓冲区剩余空间小,意味着消费速度慢,处理能力弱,发送方接下来就要发送的慢一点。
2.3.6 拥塞控制
流量控制,仅仅考虑了接收方的接收能力,但发送方的速度仅仅取决于接收方吗?不是的,还得取决于中间路径,拥塞控制,考虑的就是通信过程中中间节点的情况。
根据木桶原理我们知道,接收能力取决于能力最小的那一个,一旦中间有一个节点出现问题,那么都可能对发送方产生影响。
流量控制中,接收方的接受能力很容易量化,但是中间节点这么多,没办法量化,该怎么办呢?此时就要采用“实验”的方式。
让A先采用较慢的速度发送数据,如果没有出现问题,那么逐步提升发送速度,随着发送速度的增大,中间某个节点的接收能力可能会达到上限,此时就会出现“丢包”问题,发送方就需要调整自己的速度,如果降低速度之后还丢包,就继续降低,如果不丢包了,那就再加速试试。
这就告诉我们,别没事就吵架,吵架之后阈值越来越低,很有可能有一天会分手的~
流量控制和拥塞控制都是在限制发送方的发送速度,而最终的发送速度,取决于流量控制和拥塞控制中得到的较小值。
2.3.7 延时应答
当发送方给接收方发送数据时,接收方可以立即返回ACK,但是接收方也可以等一会儿再返回ACK(延时应答),结合上面的流量控制能知道,多等一会儿,接收方的接收缓冲区就越大,那么返回ACK之后,发送方的发送速率就会相对更高一些,这就是延时应答的作用。
2.3.8 捎带应答
在延时应答的基础上,进一步提高效率
2.3.9 面向字节流
TCP是面向字节流的,那么就会出现面向字节流的一个很严重的问题,“粘包”问题,当多个数据包被连续发送过去时,就可能会出现这个问题:
在B看来收到的数据是这样的:
那么UDP为什么不会出现这样的问题呢?
因为UDP是面向数据报的,也就是一个一个封装好的数据,B可以很容易的分辨每次的数据
如何解决“粘包”问题:
- 1、引入分隔符
此时,每次读到\n就会停止,此时B就能知道读完一次的数据了
- 2、引入长度
2.3.10 异常情况的处理
如果在使用tcp时,出现异常情况,该如何处理?
- 进程崩溃:进程没了,异常终止了,文件描述符表也就释放了,相当于调用了close方法,此时就会触发FIN,对方也可以正常发送ACK和FIN,这边再发送ACK(正常的四次挥手断开的流程),进程没了,TCP不一定没(TCP可以独立于进程存在)
- 主机关机:如果在关机前收到了对方发来的ACK和FIN,那么发送方可以正常发送ACK,此时是正常的四次挥手,类似于1;但是如果 在收到ACK和FIN之前,主机已经关机了,那么发送方就再也收不到发来的ACK和FIN了,自然也就无法返回ACK,对端不知道是什么情况,就会一直超时重传,重发FIN,发几次之后发现还是没有反应,此时就放弃了,自己断开连接(释放保存的对端信息)
- 主机掉电:该情况和1、2是不一样的,因为掉电是一瞬间的,进程和主机根本来不及反应,站在对端的角度,对端是无法知道发生了什么的。如果对端在发送数据(接收方掉电),那么收不到对方发来的ack,就会一直超时重传,如果一段时间都没反应,此时就断开连接;如果对端在接收数据(发送方掉电),对端一直在等待数据的到来,但是一直没有,此时它是不知道发生了什么的,究竟对方是没有发数据呢还是挂了呢?不知道,所以TCP引入了“心跳包”机制,接收方也会周期性的给发送方发一个特殊的、不携带业务数据的数据包,并且期望对方给一个应答,如果对方没有应答,并且重复多次之后还没有应答,此时就认为对方挂掉了,自己单方面释放连接
3 网络层
网络层要做的事情,主要是两方面:
1、地址管理:制定一系列的规则,通过地址,描述出网络上一个设备的位置
2、路由选择:网络是十分复杂的,从一个节点到另外一个节点可能会存在多种不同的路径,此时就要选择出最合适的路径进行数据传输
3.1.1 地址管理
IP地址是一个32位的整数,意味着它的长度是有限的,大概是42亿9千万,这个数字是很小的,很容易用完,那怎么解决IP地址不够用的问题呢?有如下的解决办法:
- 动态分配IP
- NAT机制(网络地址转换)
- 使用ipv6(最好最直接的办法)
这里我们重点介绍一下NAT机制:
NAT机制把网络分为两个大类:
- 内网IP :如果一个地址是10.*或者172.16.*---172.31.*或者192.168.*格式的,那么它就是内网IP,同一个局域网中,内网IP不能重复;不同局域网中,内网IP可以重复(内网是不能直接访问外网中的设备的)
- 外网IP:除内外IP外,剩余的均为外网IP,注意:外网IP是一定不能重复的!!!
我们在上网时,路由器会自动将我们的内网IP转换为一个外网IP去访问外网,在服务器看来,数据的源IP就是外网IP,它返回数据时填写的目的IP也是这个路由器提供的外网IP值,当发送给路由器时,路由器会根据之前保存的转换关系,识别这个外网IP是由哪个内网IP转换而来的,然后将返回的数据发送给它(路由器其实相当于一个中转站)
3.1.2 网段划分
3.1.3 路由选择
路由选择,其实就是找路的过程,从A到B,有很多路径可走,此时我们要做的就是找到最佳的路径(找不到“最优解”,只能是“较优解”)
路由选择是一个探索式的过程,每个路由器中都保存着一张路由表,我们可以根据目的IP查路由表,如果查到了,就按照路由器给定的方向继续转发;如果查不到,路由表里有一个“默认表项”,(下一跳地址),按照默认的表项转发即可。
4 数据链路层
mac地址是设备出厂时就写好的,可以作为一台网络设备的身份标识,mac地址的作用和IP地址其实是相似的,都是想要找到下一步该往哪里转发,区别是IP地址立足于全局,mac地址立足于局部,关注相邻两个设备之间的通信过程。
重点:每次到达一个设备时,源IP和目的IP是永远不变的,但是源mac地址会变为上一个设备的mac地址,目的mac地址会变为下一个想要到达的设备的mac地址!!!
5 域名解析系统
最后介绍一下DNS应用层协议,这个是我们上网时必不可少的东西
什么是域名呢?www.baidu.com这就是一个域名,那域名又是怎么得来的呢?是通过IP地址转换而来的。
我们知道,IP地址描述了设备在网络中的位置,但是IP地址由32位的点分十进制数组成,日常生活中很难使用和记忆,所以引入了域名解析系统,作用就是能把域名翻译成对应的IP(我们在上网时,输入网址之后,就会通过域名解析系统自动将这个域名解析为IP地址,从而找到它在网络中的位置),可以将其理解为我们前面提到过的键值对。
但是问题又出现了,全世界有着无数的设备无时无刻的在访问服务器,服务器能承受这么高的访问量吗?不会崩溃吗?
一个服务器的承受能力是有限的,访问量过高当然无法承受,承受不了那自然就崩溃了,所以我们要想一些解决办法来处理这个问题。这种问题其实就是“高并发”问题,那么,该如何解决呢?其实核心思路就两个:
- 开源:将DNS系统开源,让各个运营商搭建一组“DNS镜像服务器”,镜像服务器的数据,都从他们这里同步,用户在访问时优先访问距离他们近的服务器,采取这种方法,就可以降低用户对于DNS主系统的访问量,避免崩溃
- 节流:让请求量变少,让每个上网的设备,搞“本地缓存”,假设我的电脑一分钟之内要访问十次www.baidu.com,那么只有第一次去访问DNS,把请求得到的结果保留在本地,后面9次的请求直接通过本地缓存访问即可(能使用这种方式的原因是域名变换的并不频繁)