目录
1. 应用层
2. 传输层
2.1 UDP协议
2.2 TCP协议(*三次握手四次挥手重点!!!)
2.2.1 确认应答机制
2.2.2 超时重传
2.2.3 连接管理
2.2.4 滑动窗口
2.2.5 流量控制
2.2.6 拥塞控制
2.2.7 延迟应答
2.2.8 捎带应答
2.2.9 面向字节流
2.2.10 TCP中的异常处理
2.3 TCP和UDP之间的对比
3. 网络层
3.1 IP地址不够用的问题解决方案
3.2 地址管理
3.3 路由选择
4. 数据链路层
5.从浏览器这里输入一个URL,到最终展示出页面,这里大概会发生哪些事情(面试题)
本篇将详细介绍TCP/IP协议中的每一层里面的核心内容
应用层是和应用程序密切相关的,不同的应用程序,里面可能会涉及到不同的应用层协议。
在写代码时有相当一部分的工作量,就是在自定义应用层协议
自定义应用层协议从两方面考虑:【都是根据需求】
a)考虑要在客户端和服务器之间传输哪些信息
b)考虑信息/数据按照啥格式组织
例子:点外卖
典型场景:展示商家列表
针对这个场景,如何自定义协议
1. 明确传输的信息,请求里有啥,响应里有啥
请求:用户的位置,用户的喜好
响应:商家列表,要包含多个商家信息,每个商家包含:商家的名称、图片、评分、距离
2. 明确数据的格式
网络上传输的数据本质都是bit流(一堆二进制位),也可以把这些数据视为“字符串”
比如:
请求:经度,维度;偏好炒菜,小炒泡馍,面食;
(请求中有两部分,位置信息和偏好信息。中间使用 ; 来分割
位置信息和偏好信息内部的各个部分使用 , 来分割)
响应:XX小炒泡馍,图片,5星,800m;
XX炒菜管,图片,5星,1km;
(响应中包含多个商家信息,每个商家占一行,每行里面多个属性之间使用 , 来分割)
这个格式,在自己写代码时可以根据自己的喜好来写,并不是一定要这样写
但是写代码太灵活了也不好,所以就有一些比较特定的数据传输格式,基于这些常用的格式来传输数据,就可以更通用,也更方便
1.HTTP应用层中最重要的,也最常用的协议
2.XML比较典型的数据组织格式,不过现在不常用了
格式非常有特点,是通过“标签”的形式来组织 键值对 数据的。
例如:商家列表功能
请求:
aaa
bbb
炒菜
小炒泡馍
XML缺点:a.丑 b.数据多了编写复杂
c.这些数据要通过网络编程,消耗网络带宽的
由于这里包含了大量的标签,就导致网络带宽占用的的更高了
3. JSON当前最流行的一种数据组织格式。相当于XML的替代品
{
position:{
jingdu:aaa,
weidu:bbb
},
preference:{
'炒菜','小炒泡馍','面食'
}
}
JSON首先是一个{},{}里面包含多组键值对,键值对之间使用,来分割
键和值之间,使用:来分隔
键只能是字符串类型,
值可以是字符串,数字,数组,json
JSON优点:a)可读性非常好 b)看起来美观整洁 c)扩展性强
JSON缺点:引入额外的字符串,传输数据量变大了,消耗更多的带宽
4. 前面的XML和JSON都是属于占用带宽较高,效率较低的格式,但也有些格式,能够更高效的组织数据,其中最典型的叫做 protobuffer(谷歌搞的)
protobuffer既是一个数据格式,同时也是一个库,来操作这种格式的数据
这是一个二进制的格式,这种格式下,组织出来的数据占用空间要比JSON和XML传输效率有明显提升。但二进制不方便阅读,所以这个格式适用于运行效率比较高的场景来使用
传输层是负责端到端之间的传输,重点关注的是起点和终点
核心的协议有两个:
UDP:无连接,不可靠传输,面向数据报,全双工
TCP:有连接:可靠传输,面向字节流,全双工
课本上是这样画的,但实际应该是这样的
如果需要使用UDP传输一个比较大的数据,就需要考虑进行拆包,把一个大的数据报,拆成多个小的。但是在应用层这里对数据进行拆包,拆为多个UDP数据报,分别进行传输,开发起来会比较复杂,测试起来也比较复杂,风险较高
所以直接使用TCP,TCP是字节流的,没有对包的长度做出限制
UDP的校验和使用一个比较常见的CRC算法(循环冗余校验)
把UDP报文中的每个字节,都进行累加,加和 也放到 两个字节 的数字中,加的过程中如果溢出了,就溢出,最终得到的就是校验和
TCP的基本特性
面向字节流,有连接,全双工,代码中都是有所体现的
但是可靠传输(TCP中最核心的特性!!!),在代码中体现不出来
可靠传输指的是,数据传输过去之后,发送方知道我发成功了还是没成功
(1)确认应答机制:我发送一条数据,你给我来个应答(应答报文,也称为ack报文),通过这个应答,我就知道我这个数据是不是发出去了
确认应答机制,就是TCP保证可靠性的最核心机制
(“三次握手四次挥手”只是一定程度上保证可靠性,但这不是核心的!!!)
网络上可能会有一种情况,“后发先至”(后发的请求,可能会先到达对方这里)
网络上通信传输的路径,是复杂的,两点之间,两个报文走不同的路线
但可以在确认应答这个机制中,引入序号来保证不出现歧义(给请求和响应加上编号)
(2)超时重传
确认应答描述的是,数据报顺利到达对方,对方给了个响应
但是传输过程中,可能会丢包,如果丢包怎么办
a. 丢包的原因:网络环境极为复杂,我们能上网,是因为接入了运营商的网络。运行商这边就有很多的路由器/交换机,共同组建出一个非常庞大复杂的网络。某个交换机上,不单单是传输我的数据报,也在传输别人的数据报,某个时刻,极多的数据报都要经过这个交换机。交换机的转发能力不是无上限的,很多数据报走到这里,导致达到交换机的转发上限,无法快捷的完成转发了,就可能会导致有一部分数据报就超时了。
b. 如果数据掉包了,此时就要考虑通过“超时重传”来进行了
接收方因为丢失ACK就会导致收到重复的消息,TCP就会针对相同的消息进行去重(根据序号来去重就可以)
c. 超时时间是如何确定的
一般系统里面会有一个配置项,描述超时的时间阀值
比如:第一次出现丢包,发送方就会在达到超时时间阀值之后,进行重传。如果重传的数据仍然无响应,还会继续超时重传。第二次的超时时间一般要比第一次更长。
超时时间并非均等的,而是逐渐变大的(没有具体的数字)
这样重传,重试几次之后,仍然无法传输,就会尝试重置TCP连接(断开重连)
如果还是连不上,此时就直接释放连接(彻底放弃)
(3)连接管理:描述的就是TCP建立连接和断开连接的过程
TCP连接只是“逻辑上的”“虚拟上的连接”
a. 建立连接(双方建立一个相互认同的关系,三次握手)
三次握手(本来是四次,但是中间两次,可以合并在一起)
客户端申请一下,尝试建立连接(三次握手只能是客户端先握)
通信双方,各自向对方申请,尝试和对方建立连接,然后再各自给对方回应
建立连接的过程其实是四次的数据交互
从TCP角度看建立连接(面试考!!!)
为啥要建立连接,建立连接的意义是什么
1)检查一下当前的网络情况是否是通畅的
(三次握手建立连接并不传输任何业务数据)
2)三次握手同时也是在检查通信双方的 发送能力 和 接收能力都是正常的
3)三次握手过程中,也在协商一些重要的参数
两个重点的TCP状态:
LISTEN:服务器启动之后,绑定端口之后(new ServerSocket 完成)
表示 手机开机,信号良好,就可以让别给他打电话了
ESTABLISHED(E stable stablished):连接建立好了之后的稳定状态
表示电话接通,可以说话了
b. 断开连接(双方取消相互认同的关系,四次挥手)
通信双方,各自向对方申请断开连接,再各自给对方回应(客户端或服务器不管谁先挥手都可以)
四次挥手 不一定能合并为三步,这是因为一个是操作系统内核负责,一个是应用程序负责,所以不能合并,但TCP里还有
两个重点的TCP状态
CLOSE_WAIT 等待代码中调用close操作
如果服务器上出现大量的CLOSE_WAIT状态的连接,说明代码bug!
说明close没有被及时调用到
TIME_WAIT 主动发起关闭的一份,会进入到TIME_WAIT
A处理完最后一个ACK之后,不能立即释放连接,而需要保持一定的时间
这个是为了万一最后的ACK丢了,还有机会进行重传
(4)滑动窗口(提高TCP传输效率的有效机制)
TCP能够保证可靠传输,但是失去了效率
所以TCP希望能够在保证可靠性的前提下,尽可能的提高传输效率(尽管TCP再怎么提升效率,效率也不可能超过UDP),这就引用了滑动窗口来提高传输效率
a. 没引入滑动窗口前,效率非常低,一问一答
b. 引入滑动窗口后,批量发送消息
前面都是正常情况下。但如果出现丢包 / 乱序 怎么办
丢包两种情况:1.传的数据丢了 2.响应的ACK丢了
1. 数据包已经抵达,ACK被丢了
2. 数据包直接丢了
上面的,只是把丢的数据包进行了重传,没丢的包,并没有重传,(没有做任何重复多余的工作),所以这样的重传,效率是比较高的
这也叫做 “快速重传” (搭配滑动窗口机制的超时重传)
虽然,这里说了快速重传。但并不是说 超时重传 就没作用了
如果传输的数据很多,批量传输,那么自然是遵守 快速重传 的方式
如果传输的数据很少,此时仍然按照 超时重传 的方式进行(比如传输中最后一个丢了,那么这就可以用超时传输了)
TCP滑动窗口,窗口大小越大,确实发送速度会更快,但是窗口不能无限大
发送的速度快了,但是接收方,处理不过来啊,这就会导致接收方就丢弃一部分数据
TCP是要保证可靠性(最重要的)的,TCP还得重传这些数据,(这就是让本来不富裕的接收方,更qiong了)
这就需要再有其他的机制,来对发送发的发送速度,做出限制
(5)流量控制:在滑动窗口的基础上,对发送速率做出限制的机制
就是限制发送方的窗口大小不要太大
a. 那么窗口大小,应该多大,怎么来规定这个大小?
这就要问一下接收方,看接收方,认为发多快合适
接收方对于发送方的反制,接收方根据自己的接收能力,来反向影响发送方接下来的发送速率
b. 那么接收方的接收速率,如何进行量化?
接收方使用接收缓冲区的剩余空间大小,来作为发送方发送速率(窗口大小)的参考数值
16位窗口大小+选项(扩展因子)
流量控制,是通过接收方的处理能力,来衡量发送方的速率的
(6)拥塞控制:将中间这些结点看为“整体”,进行动态平衡的调整
前面只是考虑了接收方的处理速率,但处理方难道就可以随便的发送了吗?
显然也不是,还得考虑中间这些转发节点的情况
拥塞控制,从小的开始,逐渐变大,如果丢包再变小,反复动态调整【宏观策略】
流量控制,在控制发送方的窗口大小
拥塞控制,也是在控制发送方的窗口大小
有分歧的时候,听最小的
那么TCP实现拥塞控制具体方式是什么? 慢启动机制
上面的这个过程,是TCP传统的拥塞控制的实现(以前是这样搞的)
而后来的TCP也在进行演化,拥塞控制也做出了一些改进
其中的改进就是回归不再回归到初始值了,而是回归到一个中间的值
(7)延迟应答:提高传输效率的机制,又是基于流量控制,来引入的提高效率的机制
延时应答核心思路,只要把ACK返回稍微迟一点,就这个短时间段里面,就可以让应用程序有更多的操作空间,来把缓冲区数据进行消耗,而这个消耗肯定会导致接下来的返回的窗口,比立即返回的窗口就要略大(窗口大了,发送数据就快了,整体传输效率就快了)
(8) 捎带应答,基于延时应答的基础上引入的
捎带应答,不是100%发生的
(9)面向字节流:粘包问题
面向字节流中,存在一个典型的问题,叫做“粘包问题”
粘包问题:TCP自身对于应用层数据报是无法做区分的
UDP面向数据报,是不存这个问题的
一个UDP数据报,就是对应一个应用层报文
当前要想解决粘包问题,就是要在应用层协议这里进行区分
只要定义应用层数据协议的时候,明确包和包之间的“边界”就可以了
典型的方法有两种:
(1)通过分隔符,比如约定使用 ; 作为包的结束标记
(2)通过指定包的长度,比如在数据包的开头位置声明长度
(10)TCP中的异常处理
a) 程序崩溃了
进程异常退出
操作系统就会回收进程的资源,包括释放文件描述符表,这样的释放操作,就相当于调用了socket的close,执行close就会触发FIN报文,进一步的开始四次挥手
这种情况和普通的四次挥手实际没啥区别
b)正常关机(通过 开始菜单 这种方式来关闭主机)
关机的时候,系统会先强制结束所有的用户进程,和上述的那个进程奔溃类似
系统内核,会进行文件描述符的释放操作,进一步的进行四次挥手
c)主机掉电
非常突然,猝不及防
1)掉电的是接收方,发送方是不知道对面挂了,继续发数据
此时发的数据,没有ack了,发送方触发超时重传
重传几次之后,仍然无应答,尝试重置连接(复位报文段,RST),也会失败,只能放弃连接
2)掉电的是发送方,此时接收方就等着
接收方也不是就一直等待着,而是等了一会之后,就会发送一个“心跳包”,心跳包是周期性触发的,只是一个简单的不携带任何业务数据的包,存在的意义就是确认一下对方是否还在
d)网线断开
情况同主机掉电
只不过通信双方的主机都是好着的,这两端各自按照上述讲的两种情况分别进行
TCP在 有可靠性要求的场景 应用非常广泛
UDP对于可靠性要求不高,但对于传输效率要求很高的情况使用非常广泛
比如机房内部的内网传输,不容易丢包,对传输效率要求比较高,就可以使用UDP
传输层也不是只有UDP TCP 两个协议,还有其他的协议
网络层做的工作,就是在两点之间,规划出一个合理的路径,同时也需要对主机所在的位置进行定义
这里网络层做的工作:1.地址管理 2.路由选择
认识一下IP协议的报文格式
1.动态分配IP地址
设备上网的时候才分配给IP地址,不上网的时候,就把IP地址回收,给别人用
2.NAT机制
不再强制要求,每个主机都要有独立的IP,把IP地址分为两大类
a)外网IP / 公网IP
b)内网IP / 私网IP / 局域网IP
只要求外网IP 不能重复,内网IP则在不同的局域网中是允许重复的
常见的内网IP 10.* 172.16.* - 172.31.* 192.168.*
当前网络现状的就是 动态分配 + NAT
3.IPV6 能够彻底解决IP地址不够用的问题
IPv4 4字节,32位来表示IP地址 (42亿9千万)
IPv6 16字节,128位来表示IP地址 (42亿*42亿*42亿 *42亿)
所以IPv6 远远超过 IPv4 的可表示IP地址个数
(给地球的每一粒沙子都分配一个IP地址,也是足够的)
但是IPv6总体来说还没有大规模普及,当前网络现状还是 NAT+动态分配,这是因为IPv6和IPv4不兼容的问题,要想升级就要花钱更换设备
IP地址具体的规则
IP地址分为两部分,网络号和主机号
网络号:表示网段,在两个相邻的局域网中,要求网络号是不同的(同一个路由器连接的局域网)
也就是在同一个路由器中,WAN和LAN口得有不同的网络号
主机号:表示主机,同一个局域网中,主机之间的网络号是相同的,主机号必须不同
子网掩码:划分出从哪里到哪里是一个网络号
子网掩码是32位的,左半边都是1,右半边都是0(不会是10混着)
左半边有多少个1,就表示IP地址左侧的多少位是网络号
(把子网掩码和IP地址进行按位与运算,得到的结果就是网络号)
还有一种网络号和主机号的划分方式(这种方式已经特别老了,现在不用了)
这种分配方式比较死板,而且比较浪费(比如B类中,我这里只有20个设备,把这个网络号分配给我后,主机号是6w个,实际我只用了20个)
特殊的IP地址
(1)如果一个IP地址,主机号为0,此时这个IP就表示网络号 192.168.0.0,代表当前局域网
(2)如果一个IP地址,主机号为1,此时这个IP往往表示这个局域网的“网关”,
192.168.0.1代表局域网的网关(通常就是路由器的IP,可以手动修改)
网关:网关的角色一般就是路由器,看守当前局域网和其他局域网之间的出入口
(3)如果一个IP地址,主机号为全1,此时这个IP表示广播IP
(4)127.*开头,都是“环回IP” 典型的就是127.0.0.1,实际上,只要是127开头的都是环回IP
可以比较于,我要去某个地方,但不知道路怎么走,只能一路走一路问
IP数据报中,就包含了目的IP(我要去哪里),网络数据报到达路由器的时候,路由器自身有一个“路由表”数据结构,(路由表就是这个路由器认识的路),一个路由器无法认识到网络的全貌,但是可以认识附近的一部分
如果当前的目的IP路由器认识,就会给出一个明确的路线
如果当前的目的IP路由器不认识,路由器就会把数据报发给一个“更见多识广”的路由器
(在路由表中有一个默认的选项,下一跳)
但也有可能问了一圈后,也没找到目的地怎么走,比如IP地址不存在(或者不可达)。当数据报转发的次数超过TTL就会把数据报抛弃
路由过程不单单是去找一条路,也是需要筛选一条更好的路
最关键的是路由表数据结构是啥样的,以及路由表数据是咋来的
数据链路层最关键的协议就是 以太网
mac设计的时候是6个字节,所以当前mac地址还是够用的,是可以让每个主机都有独立的mac地址,这个地址是网卡出厂的时候就写好了的
由于当前mac只是在数据链路层使用,只要相邻区域内的设备mac地址不重复就好
因此在网络上,mac地址也可以作为是主机身份标识的一种方式
以太网,就属于传输过程中的基础设施了
把物理层比喻成公路,数据链路层就相当于跑在公路上的卡车
数据链路层里有很多协议,不同的协议,相当于不同型号的卡车,有的卡车载重量大,有的小
以太网就属于载重量比较小的(1500单位是字节)
此时如果要想运输更多的数据,就需要分多个卡车来拉货(IP协议分包保证的)
这个题有很多角度都可以回答,如果从后端开发来看的话