写在前面:
博主主页:戳一戳,欢迎大佬指点!
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个小菜鸟嘿嘿
-----------------------------谢谢你这么帅气美丽还给我点赞!比个心-----------------------------
IP地址组成:主机号 + 网络号
网络号:标识网段,保证相互连接的两个网段具有不同的标识。
主机号:标识主机,在同一个网段内,主机之间具有相同的网络号,但是主机号必须不同。
在同一个局域网内部,我们要求具有相同的网络号,但是不同主机的主机号要不同。在两个相邻的局域网之间,网络号要求不同,不相邻的局域网的网络号是可以相同的(这也就是不同局域网下为啥IP地址可以是一样的)。
例如:路由器的作用就是把两个局域网连接起来,你的电脑等其他电子设备连接的就是LAN口(对内网),这些设备是在一个局域网下,具有相同的网络号;外面的设备例如光猫是连接在路由器的WAN口(对外网),外网的设备是处于另外的一个局域网,现在这两个连接的局域网的网络号就必须不同。
前面说了IP地址由网络号和主机号组成,那么一个IP地址,哪部分是网络号,哪部分是主机号,我们要怎么区分?这个时候就要利用子网掩码
子网掩码格式与IP地址一样,也是一个32位的整数。其中左边为网络位,用1表示,1的数目等于网络号的长度,右边为主机位,用0表示,0的数目就是主机号的长度。
例如:我们查询一下自己的IP地址
IP地址是:192.168.0.192
根据子网掩码,我们可以知道网络号和主机号
网络号:192.168.0.0
主机号:0.0.0.192
既然子网掩码可以划分网络号和主机号,那么我们可以自己配置子网掩码来动态的调整不同情况下的网络号与主机号的长度。比如企业中服务器比较多,他们也是在同一个局域网下,这个时候我们则希望IP地址中可以有更多的位数来描述主机号,更少的位数来描述网络号,那么我们可以把子网掩码设置为例如255.255.254.0,这样IP地址的后面9位就是表示主机号了。
Mac地址:用来表示网络设备的硬件物理地址(通过网卡标识)
mac地址的作用和IP地址一样,都是为了标识主机的位置,只不过mac地址是作用于数据链路层,也就是相邻节点间的传输。每台主机都有自己独立的mac地址,这个是在出厂的时候就在网卡中写好的。Mac地址长度6个字节,48位,所以它比IPv4的地址也还是多了许多。
一般情况下,可以认为mac地址就是唯一的,但是不能排除一些不合规的网卡(山寨机),可能导致mac重复,不过因为mac地址只是在数据链路层,负责相邻节点的传输,所以其实只要保证相邻的节点之间不要重复就好。
总结来说,IP地址关注起点和终点,Mac地址关注相邻节点,一个区间的起点和终点,作用都是标识主机位置。
交换机是工作在数据链路层的设备,它的作用就是维护MAC地址转换表(MAC与端口之间的映射关系),并且根据MAC地址转发对应端口。
主机连接到交换机,并且接收数据报的时候,交换机也会进行学习记录相关的MAC地址与端口信息。后续再接受到数据报后,就会根据其指定的MAC地址在MAC地址转换表中查询到相应的端口,并将数据报通过该端口转发出去。
路由器是工作在网络层的设备,它的作用主要就是两个:网关 + 路由
1,网关
路由器作为网关,可以划分局域网与公网。同时某些路由器还可以将局域网进一步划分为不同的子网 (一般都是企业级用的路由器才可以)。
路由器上面有两个口,一个叫做LAN口,一个叫做WAN口。WAN口是作为公网端口,具有独立的网卡,具有公网IP与公网MAC。LAN口用作统筹下面的局域网,可能存在多个LAN口,每个LAN口也有自己独立的网卡,对应着该局域网的网段的IP地址与MAC地址【WAN口,LAN口的MAC地址指的是该端口的物理地址】
在划分公网与局域网的时候,有一个协议叫做NAPT协议,凡是局域网内的主机发送的数据报的局域网IP+端口 都会被替换成路由器的 公网IP与端口 。
2,路由
路由就是在复杂的网络通信环境中,规划出一条可以通往终点的线路。
冲突域是基于第一层物理层,又称之为碰撞域。一个集线器下的所有端口组成一个冲突域
集线器在接收到数据之后,只是简单的将数据转发给所有的端口。那么如果同时接收到两份数据需要同时转发,那么就会出现冲突。
交换机可以分割冲突域,交换机在接收到数据之后是转发到对应的端口而不是所有的端口,假设同时转发到不同的端口,那么则不会冲突,如果是同时转发到同一个端口,会出现冲突,所以对于交换机而言,它的一个端口对应着一个冲突域,可以进行冲突域的分割。
广播域是基于数据链路层。处于广播域下的所有设备都会接受到数据信息。
对于集线器而言,接收到广播数据报,仍然是直接转发给所有的端口,也就是说集线器的所有端口为一个广播域。
对于交换机而言,接收到广播数据报,也会转发给所有的端口,现在交换机上的所有端口是一个广播域,路由器可以分割广播域。
可以看到,一个UDP数据报的长度就是2个字节,换算过来就是64kb,64kb在现在来讲,根本放不下太多的东西,所以很多时候我们都需要将数据进行拆包处理,一般是在应用层的代码里面针对即将要传输的数据进行拆分的处理,这样其实开发起来不便捷也存在一些风险。对于大量数据的传输,最好的解决办法就是采用TCP协议,TCP协议针对字节流,不限制数据的长度大小。
校验和作用:
校验和顾名思义就是一个用来校验数据是否出错的数字。UDP采用的校验方法是CRC循环冗余校验,发送之前会将数据部分所有字节的值累加得到一个值,保存在报头中的校验和的位置,等待接收方接收到数据之后,会按照同样的方法再计算一次这个值,然后前后进行比较,就可以得知数据有不有出现错误了。
针对报头,重点关注的就是源端口目的端口,序号与确认序号,标志位,窗口大小。后续在可靠传输机制中会详细介绍。
注意:4位的首部长度,它的值表示的是有多少个4字节,也就是长度单位是字节。
标志位:
1,URG :紧急指针
2,ACK:确认序号
3,PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
4,RST:对方要求重新建立连接,携带这个标志位的报文段称为复位报文段
5,SYN:请求建立连接,携带这个标志位的的报文段称为同步报文段
6,FIN:通知对方,本段要进行关闭,携带这个标志位的报文段称为结束报文段
TCP特性:有连接,可靠传输,面向字节流,全双工
TCP对于数据传输提供的管控机制就两个方面,安全和效率。后续所说的机制都是在保证传输安全的前提下,尽可能地去提高传输效率。
如图就是确认应答机制,当接收方接收到数据之后,其系统内核会返回给发送方一个应答报文段ACK,表示我方已经接收到了数据。注意,我们所说的可靠传输中的可靠指的是可以知晓对方是否已经接收到了数据,而不是指的数据完全不出错。
注意:保证可靠传输的核心机制是确认应答机制
前面所说的报头中的序号 确认序号在这里也会起到作用。TCP会对我们数据的每个字节进行编号,也就是我们的序号;当接收方接收到数据之后,会返回一个ACK报文段,其中会有一个确认序号,表示的是下一次索要数据的起始序号位置,可以理解为接收方在告诉发送方多少号之前的数据我已经接收到了。
其实对于序号,也还可以解决后发先至的问题。现实的网络环境是复杂的,那么可能会存在你后发的数据反而先到了接收方,导致响应与请求之间的对应关系错乱,这一点利用序号就可以很好的解决,因为存在序号的标注,就知道数据之间的先后关系,那么后续接收方的响应也就可以对应起来了。
一个补充的点,可能有的同学会把ACK应答报文与我们说的响应给混淆在一起了,ack是系统内核在收到请求之后返回给发送方的,这只是一个单纯的反馈表示我收到了你的数据了;但是响应是业务上根据请求做出的反馈,这是应用程序负责的。
现实中,网路也会出现拥堵的情况,那么有时候肯定会存在数据丢了的问题。超时重传针对的就是数据丢包的问题。
丢包这里会存在两种情况,一种是ACK应答报文丢了,一种是纯数据报丢了。
2,丢失数据报
这个时候可能有人会说,那这接收方不就收到了重复的数据么。接收方确实会收到重复的数据,但是因为存在有序号,TCP协议会根据序号的重复来去除掉很多的重复数据。
既然是超时重传,那么这个特定的时间间隔是多少,怎么去进行设定?
一般在我们的系统里,会有一个配置项描述超时时间的阈值,超时时间并不是每次都均等的,而是在动态的进行增长。
例如,如果第一次出现丢包,超过阈值后进行重发,如果重发之后还是没有收到确认应答(小概率事件还是发生了),那么就会再次进行重发,只不过第二次重发的超时时间阈值一般是要比第一次长的。如果当前网络环境真的非常非常差,这样的重传了几次都没有结果,那么就会重置TCP连接进行重连,如果重连直接连不上了或者一样没有任何作用的话,那就释放连接彻底放弃了。
【这里重点是关注策略,而不是那几个特定的数字】
连接管理描述的是TCP建立连接与断开连接的中间具体过程。此连接指的是网络连接。这里重点介绍的就是TCP的三次握手与四次挥手,也是面试的重点。
三次握手的作用:
- 探测传输线路是否正常
对于我们的同步报文段以及应答报文ACK也都是走的网络线路,所以通过三次握手就可以基本检测网络线路有不有问题,如果SYN与ACK的传输都成问题,那么数据报就更不用说了。
- 检测双方的接收,发送的功能是否正常
- 在握手过程中,也在协商一些重要的参数
TCP中的很多参数都是需要进行协商的,比如我们的序号,并不是每次都是从1开始的,通常都是建立连接的时候协商了一个数字,这样可以来区分不同的连接。
建立连接与断开连接的时候,客户端与服务器的TCP层的状态变化:
上述的这些状态,不需要硬记,但是需要知道,重点关注批注了的几个状态。
注意这里无论是什么效率机制,都是要在保证了可靠传输的前提之下去提高效率的。
因为TCP的可靠机制,所以无论是接收方还是发送方,都会涉及到等待ACK的过程,那么效率自然是会收到很大的影响。滑动窗口机制就是为了缓解这一点。
滑动窗口就是避开了一条数据等待一次ACK的过程,发送数据就是一组,然后后面到了一个ACK就又发一条数据,类似于一个窗口在往后移动,图解如下:
图中仍然在窗口中的数据就是还在等待确认的数据。
这里的窗口的大小就对应着我们TCP报头中的窗口大小属性,窗口越大,一次性发出的数据就更多,效率相对就会更高。但是实际窗口大小是需要动态调整的,并且也不会特别大,因为还要考虑中间设备的转发能力,后续会讲。
滑动窗口既然是一次性发多条数据,那么乱序,丢包问题肯定是避免不了的。那么如何进行解决呢?
丢包问题:丢失ACK或者丢失数据报
丢失ACK,数据报都正常到达:
对于丢失ACK,在滑动窗口这里,因为是一次性发了多条数据,所以即使ACK丢了,做法就是不需要做任何处理。这里ACK具有一个累计确认的效应,数据报都是正常到达了,你中间丢了一个ACK,但是后面的ACK可以代表前多少多少的数据已经接收到了,就直接涵盖了前面的数据,所以这里ACK丢了不需要做任何的处理。
丢失数据报:
对于数据报丢失,那肯定没办法,只有进行重传。不过这里的重传机制,是只会重传丢了的数据报,不会所有的都进行重传,并且因为不需要等待ACK,速度会比较快。这种方式叫做快速重传机制
前面我们还学了一个超时重传,这两种重传机制之间是一个相互配合的关系。当数据量很大的时候,利用滑动窗口那么肯定会采用快速重传的方式;如果数据量比较少,比如就一条数据,那么选用超时重传就好了。
并且这两个判定数据丢了的方式不一样,对于超时重传是一直等到等待ACK的事件超过了时间阈值,才会去进行重发;对于快速重传,它不需要关注时间,只要当发送发收到多次相同的ACK,就判定为丢包,会进行重发。
前面我们说过滑动窗口不能够无限大,需要考虑到接收方的接收能力。那么流量控制机制就是用来限制窗口大小的,保证发送方的窗口大小在一个合适的值。
至于这个窗口大小的值,自然也是由接收方来进行决定,接收方会根据自己接收缓冲区的情况来动态影响窗口大小。
接收缓冲区就类似于是一个蓄水池,我们利用缓冲区的剩余大小来量化窗口大小。这个值会放在ACK报文的16位窗口大小属性中返回给发送方,发送方下次的窗口大小就会根据这个值做出调整。
这个窗口大小的属性是只在ACK报文中有效,并且在选项中,会有一个窗口大小扩展因子,最终的窗口大小是其左移扩展因子之后的值,而不是仅仅只有64kb。
具体过程如下:
当接收方缓冲区满了,反馈给发送方,窗口大小是0,也即是让发送方暂停发送数据。但是后续接收方的应用层会从缓冲区取走数据,那么就有了剩余空间,对于发送方,它也不可能就一直不发数据了,在一定时间后,会发送一个窗口探测报文,仅仅用作探测接收方的缓冲区剩余大小,获取到一个窗口大小的值。
我们都知道,网络传输不是一条线路,然后两个主机之间直接通信,中间会经过转发等等。如果说流量控制机制是考虑的接收方的情况,那么拥塞控制机制考虑的就是中间节点的情况。这两个值都会影响到最终发送方的窗口大小的值,木桶原理大家也知道,最终决定权肯定是两者中较小的哪一个值。
那么中间节点的情况肯定是很复杂的,肯定没有一个统一的量化标准,最好的办法就是做实验,一次次的试探,然后进行调整。
这里TCP有一个慢启动机制,在发送之初,因为网络环境未知,所以会先只发送很少量的数据进行探路,如果没有出现丢包等情况,那么就会慢慢提高窗口大小,开始是指数型增长,达到阈值之后,变为线性增长。等到后面出现丢包之后,拥塞窗口的值回归到慢开始初始值,并且调整阈值为丢包窗口大小的一半。
对于刚开始的指数型增长,是为了可以快速接近阈值,达到阈值之后线性增长,是为了防止直接翻出了极限值而导致大量数据的丢包。
现在随着TCP的发展,又引入了一个快恢复机制,也就是当出现丢包之后,窗口值不会直接一下回归到初始值,而是中间的一个合适的值,这样就不会造成传输速率的一个陡然下降。
延时应答机制是基于流量控制的一种提高效率的机制。
延迟应答,就是不要让ACK返回的那么快,延迟一会儿再返回,这样窗口大小就可以稍微大一些。注意这里是在流量控制机制的基础之上,所以不会说因为你延迟了一会儿就导致发送方窗口大小太大而导致的丢包等问题,在延迟的时间内,发送方的窗口也是处于一个合适的值。我们的宗旨就是在抱枕了可靠传输的前提下,尽最大的可能会提高传输速率。
这里的延迟应答,也不是说每次都会延迟应答,一般是选择每隔N个数据报就延迟应答一次,或者超过了最大延迟时间就延迟应答一次。
捎带应答机制又是基于延迟应答机制去实现的,因为正是ACK延迟返回给发送方了,可能刚好就可以和响应的返回时机遇到,那么就可以搭一个顺丰车。发生捎带应答是一个偶然发生的问题。
前面我们再说四次挥手的时候,有可能会编程三次挥手,原因就是捎带应答,使得FIN和ACK遇到了一起。
我们知道TCP是面向字节流的,所以数据都是一个一个字节的存储,那么现在就会有一个问题,无论是接收方还是发送方,都需要去自己的缓冲区读取数据,那么各个不同的数据怎么区分,因为都是逐个字节存储,中间又没有什么分割标记,所有的数据都粘连在了一起,这就是粘包问题。
那么怎么解决粘包问题呢?
解决粘包问题的关键就是指明数据之间的边界,所以只需要在应用层协议中,明确出各个数据之间的边界就好了,主要是两种方法:
1,确定分隔符,约定使用什么分隔符来分割数据
2,使用长度限定,在每个数据包的报头中声明数据的长度
【这两点后面在学习HTTP协议的时候会详细介绍】
程序崩溃了也就是进程异常退出,这个时候操作系统会回收进程的相关资源,比如文件描述符,那么这样的一个操作就相当于是在调用Socket的close()的方法,也就是要断开这两个主机之间关于这个进程之间的通信,这个时候内核也会发送结束报文FIN,所以两个主机之间会正常的进入四次挥手的步骤。所以其实这里是不会有任何的问题的。
主机在关机之前,其实会强制性的结束所有的进程,然后就会对文件描述符进行释放,进而通过内核发送FIN报文,正常的进入四次挥手的步骤。
主机掉电这里有两种情况,可能是发送方的主机突然掉电,也可能是接收方的主机突然掉电。
(1)接收方主机掉电
当接收方的主机掉电之后,那么发送方此时发送的请求就没有ack了,然后过了超时时间就进行超时重传,重传几次,那同样肯定还是没有ack,发送方就会尝试重新建立连接(发送一个复位报文段),照样还是会失败,此时发送方就会放弃就直接释放连接了。TCP报头中的标志位中有一个RST,当它为1的时候就是复位报文段。
(2)发送方主机掉电
发送方主机掉电之后,那这个时候接收方就接收不到数据了,那对于接收方而言,肯定也不能一直干等着,在等了一阵之后,就会发送一个“心跳包”,这个心跳包不包含任何业务层面上的内容,它的作用就只是检测一下对方还在不在。如果这个时候发送方不给接收方回发一个心跳包,那么接收方就知道是发送方挂了,进而就直接放弃连接了。
网线断开的情况同主机掉电,只不过此处主机都是正常的,接收方和发送方分别走上述的两种情况。
对于TCP与UDP,各自有各自合适应用的场景,TCP适用于对于数据可靠性要求高的场景,UDP适用于对于数据传输速率高的场景。
那么这里就会有一个面试题,请问如何实现UDP的可靠传输?
看似他是在问UDP,其实是在考察TCP的可靠传输机制,TCP是自己带有,那么UDP我们将TCP的可靠传输机制迁移过来进行实现,在应用层通过一些方法来模式类似于确认应答,超时重传的机制,序列号等等。【确认应答,超时重传是重点】