TCP/IP协议
1)应用层
应用层和应用程序直接相关,与程序员息息相关的一层协议,应用层协议,里面描述的内容,就是写的程序,通过网络具体按照啥样的方式来进行传输,不同的应用程序,就可以用不同的应用层协议,在实际开发的过程中,需要程序员自制应用层协议
应用层协议本质上就是对传输的数据做出约定
1)约定传输的数据有哪些信息
2)传输的数据要遵守啥样的格式
举个外卖的例子
信息:
1)请求:用户的ID,地理位置 响应:商家列表等等
格式
数据一般都是"结构化数据"(通过结构体的形式来表示数据),网络上传输的是字符串(二进制字符串),就需要对数据进行序列化
序列化的方式
1)行文本:直接以文本的形式显示出来
2)具有xml的方式,这是一种经典的数据组织形式,如下是他的格式,标签中的名字是啥,如何嵌套可以自己实现,html是一种特殊的xml,html里的标签都是有一套标准的规范,不能自己定制,以后可以通过maven来进行管理xml
3)基于json的格式,当前最流行,广泛的形式,用{}来作为边界,键值对之间使用,分开,键和值之间使用:分开
4)yml,基于缩进的格式
5)protobuffer(pb),前面几种都是文本格式,pb则是二进制格式,针对要传输的数据进行进一步的整理和压缩,能够把空间最充分的利用,最节约网络宽带,效率最高
也有一些现成的协议,如http可以供我们直接使用的
2)传输层
传输层也是跟程序员息息相关的一层,
传输层为引用层提供协议,比如socket API都是传输层协议提供的
端口号
IP地址(确定主机地址)网络层提供的概念 + 端口号(确定主机上的应用程序)应用层提供的概念,端口号是无符号两个字节的整数 0-65535
有一些知名端口号是把小于1024的,作为这些服务器的默认端口号,这些服务器是指上个世纪,比较知名的服务器,程序员自己写的程序绑定端口号,要避开知名端口号,在同一个协议下UDP/TCP,如果两个服务器都是同一个协议,那么他们绑定的端口号不能相同,如果相同会出现如下报错
如果是一个UDP,一个是TCP两者同时启动,不同的协议之间是可以的不会报错
我们可以提供命令行指令netstat -ano来查询出当前机器上进程的端口号
这一行的命令是用来查询端口号为9090的进程,这个符合 | 表示管道是Linux上的指令,表示把前面的返回结果给后面的,然后后面的在前面的返回结果中查询,9090前面是0.是ipv4 [::]是ipv6
一个服务器进程是可以绑定多个端口号的,这个写法在实际开发中非常实用
比如在写一个服务器程序的时候,首先服务器需要有一个端口号,这个端口号是给客户端提供业务功能,这端口称为"业务端口",
此外程序员还需要对服务器有更精确的控制,比如控制这个服务器重新加载配置/开启某个功能/重新启动/重新加载数据/修改某个功能,对于这样的操作,经常会通过网络来进行操作,服务器就需要绑定另外一个端口了,称为"管理端口",这个端口一般是程序员对服务器进行管理操作的时候,通过这个端口来进行管理
在日常开发会遇到bug,需要去查看服务器的运行状态(比如服务器的一些关键值是什么样的值),服务器不能直接通过调试器去调试,因为调试器会把服务器给阻塞了,无法为客户端提供服务,也会通过网络的方式,给服务器发调试请求,服务器返回关键的信息,这个端口称为调试端口
传输层的协议UDP
特点:无连接,不可靠传输,面向数据报,全双工
当应用层数据到达UDP之后,就会在应用层数据报前面装上UDP的报头,UDP数据报 = UDP报头+UDP载荷 报头+载荷一共64kb 不是64kb-8
校验和的方法是:数据引入冗余信息~~通过冗余信息来验证原有的数据,校验和是要根据实际内容算出来的,校验和就是拿着数据(一部分)进行一系列计算,得到结果,如果数据发生变化,就会感知到
UDP中使用的校验和算法是CRC,其是一个简单粗暴的计算和校验的办法,循环冗余校验,设定一个两个字节的变量,把数据的每个字节取出来,往这个变量上累加,如果结果溢出,超过两个字节,溢出部分舍弃
除了CRC算法,还有md5
通过md5将字符串转化为数字
传输层的协议TCP
特点:有连接,可靠传输,字节传输流,全双工
源端口号和目的端口号与UDP的报文格式一样,记录了数据报从哪来的端口号,到哪里去的端口号
序号和确认序号:稍后了解
4位报头长度:指的是报头的长度,不是总TCP报文的长度
选项字段:可以理解成选择性,比如在买电脑的时候,选择水冷还是风冷这些配置,
如果没有选项,TCP报头长度就是20个字节,如果选项拉满就是60个字节,因为4位表示的是四个比特位0-15,报文长度的单位不是一个字节而是四个字节,所以最大长度就是60个字节,去掉固定长度20个字节,选项最大长度就是40个字节
保留6位:在TCP报头里,提前申请好一块空间,以备不时之需,这样的空间就是"保留位",以后需要扩展新功能就可以用这个保留位
六位标志位是TCP的灵魂
URG是与紧急指针配合使用处理特殊情况, PSH是催促对方赶快发消息的
TCP协议的核心机制
1.确认应答机制
TCP协议来说,解决最重要的问题就是可靠传输 所谓的可靠传输不是发送方100%传输给接收方,但是尽可能,让发送方知道,接收方是否收到,当发送方给接收方发送的信息,接收方给发送方进行回应,这就称为确认应答,靠对方回应给你一个应答,应答的数据称为应答报文=>acknowledge
当这个比特位为1的时候,就说明这是一个应答报文,
如果批量发送数据的时候,就会带来麻烦,因为在网络传输的过程中,经常出现一个情况是"后发先至",后发的消息先到达,可能会使信息的回应牛头不对马尾,后发先至,是网络发送的客观特点,无法改变,即使出现后发先至,也不影响传输意思的理解
实际的TCP的序号不是根据"一条两条"的方式来编号的,而是按照字节的方式来进行编号的,每个字节都会分配一个序号,针对不同的数据报
TCP报头不表示TCP长度,TCP载荷长度是要结合IP协议报头来算出
TCP协议,最核心的功能就是可靠传输,之所以能够可靠传输就是因为,"确认应答"机制,如果顺利传输成功,如果不顺利的话则会产生丢包行为
丢包:在网络传输过程中,网络情况错综复杂,最大特点就是冗余,服务器A与服务器B之间,连接了许多的路由器/交换机,然后每个路由器/交换机,转发能力(单位时间转发多少数据报)是存在上限的(取决于转发芯片的硬件设备能力),一旦某个设备,转发的数据量超出自身的极限,就会丢弃这些超出的数据,称之为丢包,丢包客观存在,无法预测,网络环境不可靠.
TCP要在不可靠的网络环境中,构造出可靠的"通信方式"通过超时重传
2.超时重传
这个专门用来应对网络丢包的情况,正常情况下TCP是通过"确认应答"来判断数据有没有被对端收到,
第一种情况,传输数据丢失
第二种情况:数据传输成功,但是ACK丢失
应用程序read的时候,就会在接收缓冲区中把数据删除,确保应用程序read的序号和发送方write的顺序是一致的
丢包本身就是"概率性事件",重传次数越多,丢包的概率越小
如果网络出现严重故障,重传若干次,还是不成功,达到一定阈值,则会尝试"重置连接"->触发一个"复位报文"(六个标志位里的RST)尝试重置连接(相当于连接重新开始)重置就是通信双方清空之前TCP传输过程中的中间状态(比如接收缓冲区中的数据都不要了),重新开始传输,如果RST还没有效果,则就会释放连接(删除对方的相关信息)
超时重传是确认应答的重要补充,TCP的可靠传输全靠超时重传和确认应答这两个机制来支持
3.连接管理
建立连接的流程:三次握手
两个机器一见面就进行打招呼,没有什么实质上的数据交互,就是为了打招呼而进行一下数据传输
建立连接,就是通信双方,各自保存对端的信息,需要经过三次网络交互的过程
双方都要发送syn的原因:三次握手的时候,相当于双方各自让对方保存自己的信息,得是双方把对方的信息保存好了之后,才算建立连接成功
SYN就是要保存对方的IP(IP报头里面)和端口(TCP报头里面),
应用层是应用程序,传输层+网络层是操作系统内核,数据链路层是驱动程序,物理层是硬件
三次握手的意义: 1)相当于投石问路,在正式传输业务数据前,先确认通信链路是否畅通
2)通过三次握手确定通信双方,发送能力和接收能力是正常的
3)三次握手过程中,接收方和发送方还需要对必要的参数进行协商,比如起始序号
可不可以两次通信:不可以,如果只有两次通信这不能确定通信双方的发送能力和接收能力是正常的
TCP通信时使用的序号,就是协商出来的,序号往往不是从0/1开始的,而是三次握手的时候协商出来一个数字,第一次连接和第二次连接的协商出来的起始序号不同(差异很大),因为第一次连接成功之后,要传输很多的业务逻辑,由于网络的原因,有一个业务逻辑数据走丢了,过了很久才传输到服务器,但是此时第一次连接已经断开,此时已经是第二次连接了,第一次的业务逻辑数据传输给服务器,此时服务器就需要判断这个序列号来丢掉这个数据,否则会出现问题
在三次握手的过程中TCP的状态转换
listen:是服务器出现的状态,当服务器绑定端口成功之后,就会进入该状态,此时意味着客户端连接上来了,
established:建立连接完成,可以进行后续通信了
断开连接的流程:四次挥手(不携带载荷数据,)
在握手和挥手的过程中,传输的网络数据,没有任何业务上的数据,就好比人在相亲过程中,先通过简单的打招呼,闲聊在进入正题,而不是直接进入整体
四次挥手的重要状态
CLOSE_WAIT(被动接受的一方):当被动的一方收到FIN没有调用close/进程没有结束的这一段时间状态就是CLOSE_WAIT,在代码里面close调用的越及时,这个状态就越不容易看到,有时候在服务器中见到了大量的CLOSE_WAIT=>说明代码可能出现了bug,close忘记调用或者close调用的不够及时
TIME_WAIT(主动发起请求的一方):就是为了应对最后一个ACK丢包的场景,这个状态不是持续的,而是有一段时间的,过了这段时间没有返回fin,就证明收到了ack,不会重传fin了,这段时间一般是2MSL,也就是两个节点之间传输数据的最大时间,一般是1min,如果2min之后没有收到重传fin,TIME_WAIT状态就释放了
经典面试题:如果服务器中出现大量的TIME_WAIT状态是什么原因:服务器这边出现了大量主动断开TCP连接的操作,(这个操作对服务器来说一般是不科学的,一般是客户端主动断开连接)
四次挥手的注意事项:
释放TCP连接,相当于通信双方把对端的信息全部删除,服务器调用close不是立即结束,而要告知客户端,我也要结束了,等到客户端和服务器都收到各自传输的ack之后,才会断开连接
4.滑动窗口
窗口指的是:不等待返回ack,批量发送多少数据,这个过程就是窗口的大小
如果出现了丢包,那么如何进行重传
情况1:
情况2:
这样的机制也成为快速重传,快速重传相当于是超时重传的特殊情况,
在tcp传输的数据比较少的时候,不频繁就不会触发滑动窗口,短时间传输大量的数据,才会触发滑动窗口,
重要结论:TCP为了保证可靠传输,牺牲了很多效率,引入滑动窗口,是为了提升效率,不论如何提升效率,TCP传输速度都是不可能超过UDP这种不可靠传输的协议
5.流量控制
在滑动窗口中,设计一个概念就是窗口大小(批量发送数据的多少,属于是不需要等待ack),窗口大小是可变的,窗口越大,单位时间发送的数据就越多,效率就越高,窗口越小,单位数据发送的数据就越少.
接收方根据自身的处理能力,反向制约发送方的速度,使双方达成平衡,称为流量控制
具体如何衡量接收方的处理速度
6.拥塞控制
和流量控制类似,都是和滑动窗口搭配的机制,流量控制是站在接收方的角度,影响发送方的速度,
流量控制的时候,很容易定量的来衡量,接收缓冲区剩余空间的大小,用这个来作为滑动窗口的大小,
如果考虑中间节点的话,就情况复杂了,中间有太多设备,而且每个设备走的路径,处理能力都不一样,
不论中间情况有多复杂,TCP都将他们视为一个整体,然后通过实验的方式来找到一个最合适的窗口大小(发送速度)
大致实现方法如下:刚开始按照一个特别小的速度小的窗口来发送,如果没有出现丢包的情况,此时就可以增加速度,增加窗口大小,如果还是没有丢包,就继续增加速度和窗口大小,增加到一定程度,发送速度非常快了,遇到性能瓶颈,此时可能会出现丢包的情况,此时发送方立即减小发送速度,窗口大小,继续发送看看是否丢包,重复上述过程
1.刚开始慢启动,是以比较小的窗口来传输数据
2.按照指数增长的方式来扩大窗口(*2),
3.指数增长到某个阈值之后,就要开始线性增长
4.线性增长,发送速度越来越快,传输就会出现丢包,立即缩小窗口大小
5.缩小有两种方式:1.直接缩到底 重新开始指数增长,然后线性增长2.缩小到当前拥塞窗口的一半,接下来线性增长
拥塞控制和流量控制都会得到一个窗口,他们两个谁的窗口小,就采用谁的窗口
7.延时应答
延时应答:是指ack不会立即返回,而是要等待一会在返回,目的是为了提升传输效率,在可承受范围内,尽可能提升窗口的大小,通过延时就可以提高窗口的大小,延时给应用程序更多的时间去消费数据
此处延时应答有两种策略:1)按照一定时间来指定延迟,2)按照收到的数据量来指定延迟,这两个策略结合使用
8.捎带应答
建立在延时应答的基础之上,来提升效率
9.粘包问题
在字节流读写数据的情景中,会涉及到一个非常关键的问题,粘包问题
10.异常情况
1.其中某一个进程崩溃了
进程崩溃了,正常结束,操作系统都会回收释放对应的PCB,就可以释放里面的文件描述符表,也就相当于调用close,仍然会正常和对方进行四次挥手
2.某个主机被关机了(正常流程)
正常流程的关机,操作系统会先尝试强制结束所有用户的进程,然后在进入关机流程,这个过程也会触发四次挥手
另一种情况,A和B建立TCP连接,A这边关机了,关机之前告诉B FIN,B收到了FIN之后,也会给A发送FIN,但是A主机关机了没有收到FIN,此时B,就会重复几次FIN的操作,然后还是没有收到ACK就会把A的信息删除
3.某个主机掉电了
A和B进行通信,A的主机突然掉电了,B不知道仍然等着A给他发送信息,此时有两种情况
情况一:B是发送数据方,B接下来发送的数据都没有ack,B就会触发超时重传,重传几次之后,发送复位报文(RST),RST没有相应,B就会单方面的把A的信息给删除
情况二:B是接收方,B在一定时间之后没有收到A发送的数据,就会触发"心跳包",心跳包可以认为是一个没有载荷的数据报,B给A发送一个心跳包,如果A正常,A就会回应ACK,如果A挂了,B不会收到任何回应,连续发送多次之后B没有收到回应,B就会认为A挂了,单方面把A删除掉
在实际开发中,经常会实现一个应用层的心跳包(ping->pong),用更高的频率,更短的周期
4.网线断开
本质上就是第三种情况
比如A和B建立TCP连接,此时网线断了,假如A是发送方,则A会触发超时重传,然后触发RST都无响应,单方面删除B,B是接收方,会触发心跳包,A不会返回ack,则单方面删除A
关于TCP的一个相关面试题:如何用UDP来实现可靠传输,引入确认应答(ack),引入序号+确认序号,引入超时重传
3)网络层
IP协议
网络层主要做的事:路由选择,地址管理
IP协议报头
解决IP地址不够用的问题:1)动态分配+NAT机制 2)Ipv6
IP地址基本规则
1)网段划分
同一个局域网的主机,要按照一定的规则分配IP地址
把一个地址(Ipv4)分成两部分
前半部分:网络号=>标识局域网
后半部分:主机号=>区分局域网中的不同主机
同一个局域网内部,主机中的IP,网络号相同,主机号不同
相邻的局域网(同一个路由器连接的)网络号不能相同,如果相同则不能上网
一个IP地址32位,怎么区分是网络号,主机号
引入子网掩码的概念
32位的整数,左半部分全为1,右半部分全为0,01不会交替出现
路由选择
4)数据链路层
以太网协议
目的地址和源地址都是六个字节,mac地址/物理地址,目的都是区分网络不同的上网设备,mac的作用支持两个相邻节点之间的转发
5)物理层
DNS(域名解析系统)
IP地址=>点分十进制,域名就是一串单词,表示某个/某组IP地址
这个目录下的hosts文件
在上古时期,有一个hosts文件专门维护域名和IP之间的映射关系,一个IP地址映射一个网站,
由于文件需要频繁手动更新,所有取而代之的是搭键一个DNS服务器,来保存这样的映射关系,访问域名通过访问DNS服务器的方式,查询的对应的IP地址
常用的方法
1)客户端缓存
电脑会保存访问过的域名,得到IP地址,下一次在进行重复的查询时,就不会重复访问DNS
2)分布式服务
通过搭建镜像DNS服务器,这个镜像服务器就是复制了DNS服务器,映射关系一模一样
域名体系也是分等级的