应用层和代码直接相关的一层
决定了数据要传输什么,拿到数据之后如何使用
约定应用层数据报,数据格式,就是在自定义协议~~
外卖程序,有一个核心的功能,加载商家列表
请求
用户ID
用户的位置(经纬度)
响应
若干个商家信息
每个商家信息
商家的名字
商家的图片
评分
类型
…
网络上的传输的,本质上都是0101,视为二进制的字符串
需要把上述这些信息整合成一个字符串
一个简单的方案,直接基于分隔符
请求:
用户ID
用户位置
属性之间使用,分割整个请求以;结尾 -> 可以使用任意符号作为分隔符,只要分隔符不会在正文中出现即可
响应
商家名字,商家图片地址,商家评分,商家类别\n
商家名字,商家图片地址,商家评分,商家类别\n
商家名字,商家图片地址,商家评分,商家类别\n 属性之间使用,分隔
商家名字,商家图片地址,商家评分,商家类别\n 每个商家使用\n分隔
商家名字,商家图片地址,商家评分,商家类别\n 最终结束使用;分隔
商家名字,商家图片地址,商家评分,商家类别;
一种典型的格式 xml 之前非常流行的格式,现在使用的比较少,但是也还是经常遇到~~ 通过标签的形式来组织
请求
100
100 - 100
xml 中包含了很多标签
一对标签
开始标签
结束标签
标签之间放的内容
标签可以嵌套
xml中的标签都是用户自定义的,每个标签,每个名字由你决定
html 可以视为视xml的特殊期情况
html 的标签,以及标签的含义,都是有一个标准委员会约定的,咱们只能遵守
另外一种非常流行的格式,json
{
userld:100,
userPos: 100 - 100
}
使用{}作为标识~~
{}里面是若干个键值对
每个键值对之间使用,分割
键和值之间使用过;分割
键必须是字符串
值可以是数字,字符串,数组,另一个json
学习一个协议,其中一个重要的环节,就是认识协议的报文形式
(具体怎么组织的这个数据)
每个端口号在UDP报文里,占两个字节~~
其实端口号的取值范围 0 ~ 65535
<1024 的端口,称为"知名端口号",给一些名气大的服务器预留的端口,这部分端口号我们不应该使用
报文长度 0 ~ 65535 => 64KB
一个UDP报文的最大长度 64 KB!!!
要传输一个比较大的数据怎么办??
1.可以把一个大的数据拆分成多个部分,使用多个UDP数据报来传输
2.不用UDP,直接用TCP,TCP没有限制
网络传输并不稳定
通过网线传输~ 电信号 ~ 电信号使用高低电平表示0,1
外部干扰,就会导致高电平->低电平 低电平->高电平
造成比特翻转 => 数据传输就出错了
校验和存在的意义就是用来判断当前传输的数据是否可能出错~~
校验和不能保证数据一定是对的
为了让校验和能够识别率更高一些,计算的时候通常以数据内容作为参数来进行计算
发送方,把载荷数据,带入到校验和算法中去,计算生成得到校验和结果,设为sum1
发送方把这一串数据发送给接收方
接收方收到的数据,既有载荷,也有校验和sum1
接收方就可以吧载荷按照同样的算法,在计算一遍校验和,得到sum2
对比sum1和sum2
源IP
源端口 数据从哪里来
目的IP
目的端口 数据到哪里去
有连接
可靠传输(最核心的机制)
面向字节流
全双工
核心机制,在于接收方收到了或者没收到,会有个应答~~
实现可靠性的最核心的机制!!
需要针对消息进行编号!! 给发送的的消息分配一个"编号",同时应答报文,给出"确认序号"
TCP 是针对每个字节都去编号!!
(TCP没有"一条消息两条消息"这样的说法)
从前往后,把每个字节分配一个编号~~
注意:确认序号的规则!!
不是说,发送方的序号是啥,确认序号就是啥,而是取得发送方发过来的所有数据,最后一个字节的下一个序号
确认序号1001的含义:
1.< 1001 的数据,我已经收到
2.我接下来想向发送方索要从 1001 开始的数据
为啥网络上会出现后发先至??
如果发送放发送好几条消息,可能就会导致发送的消息出现错乱
TCP会有一个接收缓冲区(一块内核中的内存空间)
每个socket都会有一份自己的缓冲区~
TCP就可以按照序号针对收到的消息进行整队~~(也是TCP序号的一个重要用途)
这样就会是应用程序读数据,读到的一定是有序的(和发送顺序一样的)
如果一切顺利,就可以直接确认应答了~ 可靠性自然得到了支持~
丢包,也是网络上非常典型的情况~
为什么会丢包呢
如果中间任何一个节点,出现了问题,都可能导致丢包~~
每个设备,都是在承担很多的转发任务
每个设备,转发能力都是有上限的
某一时刻,某个设备,上面的流量达到峰值,就可能引起部分数据被丢包~~
这样的情况下非常容易出现概率性丢包~
如果包丢了,接收方就收不到了~~自然就不会返回ack.
发送方就迟迟拿不到应答报文~~
等待一段时间后,还是没有收到应答报文,发送方就视为刚才的数据丢包了~
就会重新再发送一遍~~
发送方对于丢包的判定,是一定时间内没有收到ack~
1.数据直接丢了,接收方没收到,自然不会发ack
2.接收方收到数据了,返回的ack丢了~
发送方是区分不了这两个情况的,只能都重传~
这种情况没有问题
这种情况下
B会收到重复的数据!
TCP会在接收缓冲区中根据接收到的数据的序号,自动去重~~
保证了应用程序读到的数据仍然只有一份!!!
重传的数据有没有一种可能又丢了?
也是很有可能!!!
一旦出现连续丢包,这种情况下多半你的网络出现了非常严重的问题.
TCP针对多个包丢失,处理的思路是,继续超时重传~~
但是每丢包一次,超时等待时间都会变长~~(重传的频率降低了)
连续多次重传,都无法得到ACK,此时TCP就会尝试重置连接~(相当于尝试重连)
如果重置连接也失效了,TCP就会关闭连接~ 放弃网络通信了~
一切顺利,使用确认应答保证可靠性!
出现丢包,使用超时重传作为补充~
这两个机制,是TCP可靠性的基石!!!
TCP建立连接:三次握手
TCP断开连接:四次挥手
三次握手:握手指的是通信双方,进行一次网络交互~
相当于客户端和服务器之间通过三次交互,建立连接关系 双方各自记录对方的信息!
上述过程内核自动完成,应用程序干预不了.
等到连接完成了,服务器accept把建立好的连接从内核拿到应用程序中~~
syn 称为同步报文段(一方向另一方,申请建立连接~)
啥样的报文,算是 syn 报文 ??
观察 TCP 报头结构
6个特殊的比特位
这几位默认为0
如果设为1,则表示特定含义
其中第二位,是ACK,如果这一位为1,表示当前TCP数据报是一个应答报文
其中第五位,是SYN,如果这一位为1,表示当前TCP数据报是一个同步报文
如果一个TCP数据报,第二位和第五位都是1,则当前这个报文是SYN+ ACK
FIN这一位为1就表示一个结束报文~
为啥要三次握手~~起到了什么效果,达成了什么目的???
三次握手这个过程,本质上是投石问路~~验证了客户端和服务器,各自发送能力和接收能力是否正常!!
断开连接,四次挥手~~
通信双方,各自给对方发送一个FIN(结束报文),再各自给对方返回ACK~~
建立连接,一定是客户端主动发起
断开连接,客户端和服务器都有可能先发起~
为啥三次握手能100%合并,四次挥手就不能合并?
三次握手,ack和syn是同一时机触发的(都是内核来完成的)
四次挥手,ack和fin则是不同时机触发的.
ack是内核完成的,会在收到fin的第一时间返回~~
fin则是应用程序代码控制的,在调用到socket的close方法的时候才会触发fin!!!
这个图更详细的画了TCP三次握手四次挥手,以及中间传输的过程~~
TCP状态转换
TCP要保证的不仅仅可靠性,还有效率!!
提升可靠性,往往意味着损失效率!!
此时A这边就花了大量的时间等待ACK!!
想要提高效率,就要缩短等待时间~~
批量发送数据
一次发送多条数据,一次等待多个ack~~
这里就是批量发送四条数据,发完之后统一等待ack
每次收到一个ack,就立即发送下一条(不是收到4个ack再发送下一组)
使用一份时间,等待多个ack,总的等待时间缩短了,整体的效率就提升了~~(是和没有批量发送进行对比而不是没有可靠性对比)
上述批量传输数据的过程,称为滑动窗口
批量传输,叫做滑动窗口~~
批量不是无限发送,是发送到一定程度,就等待ack,不等待直接发送的数据量有上限的~~
而且回来一个ack就立即发送下一条,相当于总的要批量等待的数据是一致的
把批量等待数据的数量,就称为"窗口大小"
收到一个ack,就立即发送下一条.
在批量发送的过程中,如果出现了丢包怎么办?
可靠性第一,效率靠后
ack丢失
这种情况对可靠性没有影响!!
确认序号的含义表示该序号前的数据已经收到了后一个ack,能够涵盖前一个ack的意思!!
当我们收到2001这个ack的时候
此时发送方就知道了,2001之前的数据都收到了1001这个ack丢了就丢了,没有影!~
数据丢失
由于刚才的1001 - 2000 这个数据丢失了
所以接收方任然在索要 1001 ,不会因为收到的是 2001 - 3000 就返回 3001 !
接下来几次的数据的ack,确认序号都是 1001 !!!
B再向A反复索要 1001 这个数据!!
A 这边连续收到几个 1001 之后,就知道事情不简单~~ 1001 怕是丢失了!!
A就重传了 1001 - 2000 这个数据 !!!
当A把 1001 - 2000 这个数据重传,B收到之后返回的ack确认序号是 7001 !! 而不是 2001 !!
因为 2001 - 7000 这些数据,B都已经收到过了!!
上述重传过程,没有任何冗余的操作~~
只有丢失的数据才会重传,整体速度是比较快的~~ 这个重传过程也称为快速重传
滑动窗口,快速重传,是在批量传输的大量数据的时候,会采取的措施~~
如果你就只传输一条两条,少量的,低频的操作,就不会按照滑动窗口这样,任然是前面朴素的确认应答和超时重传~~
保证可靠性的机制~~
滑动窗口,批量发送
窗口越大,相当于批量的数据越多,整体的速度就越快~~
如果你发的太快,瞬间把接收缓冲区给打满了,接下来继续发送,此时数据就会丢包~~
通过流量控制,本质上就是让接收方来限制一下发送方的速度.
本质上就是让发送的慢一点,甚至阻塞下~
具体实现:
让ack报文中,携带一个"窗口大小"这样的字段
当ack为1的时候,ack报文
此时窗口大小字段就会生效
这里的值就是建议发送方发送的窗口大小~~
此时接收方如何计算窗口大小?
接收缓冲区剩余的剩余空间
此时A就根据 3000 这个窗口的大小,批量发送这些数据~~
此时,接收缓冲区就满了,此时发送方就暂停发送了
当发送方发现对方满了之后就会暂停发送,但是仍然会每隔一段时间触发一个窗口探测报文~~
如果探测一会发现对方这里不是0,腾出空间了~~(应用程序从socket读数据,就会消费缓冲区的内容,也就可以腾出空间了)
发送方的窗口大小 = 流量控制 + 拥塞控制~~
滑动窗口的大小,取决于流量控制和用拥塞控制~~
流量控制:衡量了接收方的处理能力.
拥塞控制:衡量了传输路径的处理能力.
中间节点(一系列的交换机和路由器)
传输路径上的任何一个设备,处理能力如果遇到瓶颈都会对整体的传输效率产生明显影响!!
拥塞控制 横梁中间节点传输的能力
拥塞控制是要衡量中间路径,中间路径上的点有很多,每个节点的情况不同,甚至每次传输,走的路径都不同~~
拥塞控制通过实验的方式,找到一个合适的发送速率!(动态平衡)
开始的时候,按照一个小的速率发送,
如果不丢包,就可以提高一下速率(扩大窗口的大小)
如果丢包,则立即把速率减小~
准备按照多大的速率发送数据(暂时不考虑流量控制的情况)
刚开始传输,会给一个非常小的窗口(比较小的初始速度)
慢开始
可以让窗口大小短时间内就达到一个比较大的值~ 快速接近当前网络传输路径的能力瓶颈~
指数增长达到一定阈值就变成了线性增长~ 避免一下突然上限很多,可以使传输速度,逐渐接近传输上限~
增长到一定程度,出现丢包.认为当前的窗口的大小,已经达到了当前路径上的传输上限~
此时又立即把窗口大小回归到一个比较小的初始值.
重复上述过程~~
TCP可靠性的核心是确认应答.
ACK要发,但是不是立即发,而是稍微磨蹭一会再发~~(提高传输效率)
TCP中决定传输效率的关键元素就是窗口大小!!
流量控制 接收方,接收缓冲区剩余空间大小
立即返回一个ACK,此时ACK里带有一个窗口大小,设为n
如果稍等片刻,再返回ACK,此时ACK里的窗口大小,大概率比n要大!!
(等的这一会,应用程序从接收缓冲区里消费了一批数据)
延时应答的效果,就是通过这个延时,让接收方应用程序,趁机多消费点数据,此时反馈的窗口大小,就会更大一丢丢
此时发送方的发送速率也就能快一些~~(同时也能够满足让接收方能够处理过来)
也不是所有的包都延时.
基于延时应答~~
客户端服务器之间的通信模型,通常是"一问一答"这种模式的~
客户端服务器通信模型:
1.一问一答,绝大部分服务器都是这样
2.多问一答,上传大文件
3.一问多答,下载大文件
4.多问多答,游戏串流~~
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 “一发一收” 的。意味着客
户端给服务器说了 “How are you”,服务器也会给客户端回一个 “Fine, thank you”;
那么这个时候ACK就可以搭顺风车,和服务器回应的 “Fine,thank you” 一起回给客户端
为啥四次挥手,有可能三次挥完?? 同理~~捎带应答起到的效果!!
暗藏杀机~~ 粘包问题!!
所谓的"一句话"就相当于一个"应用层数据报"
当A给B连续发了多个应用层数据报之后,这些数据就都累积到B的接受缓冲区中,紧紧的挨在一起
此时B的应用程序在读取数据的时候,就难以区分从哪到哪是一个完整的应用层数据报
就很容易读出半个包/一个半…
定义分隔符!粘包问题的有效解决方案!!!
进程没了,socket是文件,随之被关闭~ 虽然进程没了,但是连接还在~
仍然可以继续四次挥手
先杀死所有的用户进程~~
也会触发四次挥手~ 如果挥完啦,更好,如果没挥完,比如,对方的fin过来了,咱们没来得及ack就关机了~
此时对端就会重传fin~ 重传几次之后,发现都没有ack,尝试重置连接,如果还不行,就直接释放连接~
瞬间机器就关了,来不及进行任何挥手操作~~
(1)对端是发送方
对端就不会收到ack => 超时重传 => 重置连接 => 释放连接
(2)对端是接收方
对端是没法立即知道,你这边是还没来得及发送新的数据,还是直接没了~~
TCP内置了心跳包 保活机制~~
心跳包
[a]周期性
[b]如果心跳没了,挂了~~
虽然对端是接收方,对端会定期给咱们发送一个心跳包~~(ping)
咱们返回一个(pong)
如果每个ping都有一个及时的pong,这个时候说明当前对端的状态良好
如果ping过去了,没有pong,说明心跳没了,怕是挂了~~
同上
TCP和UDP的差别
应用场景
TCP可靠传输,效率没那么高
UDP不可靠传输,效率高~~
绝大部分情况下,都可以使用TCP
对于效率要求较高对于可靠性要求不高的情况下~ (同一机房内部的内网之间数据传输 分布式系统~)
传输层协议不仅仅是TCP和UDP~~
每个网络上的设备,要能分配一个地址(唯一)
IP地址,本质上是一个32位的整数~~
通常会把32位的整数,转换成点分十进制的表示方法~~
三个点,把这个整数分成4个部分,每个部分,一个字节,每个部分的取值范围 0 - 255
32位的整数最多能表示多少个不同的地址呢???够不够用呢??
42亿9千万,全世界有很多的电脑,服务器,路由器,手机会导致IP地址不够用
如何解决上述问题?
1.动态分配IP地址
设备上网才分配,不上网就不分配~ 此时就可以剩下一大批IP地址~
并没有增加IP的数量,只能一定程度上的缓解,不能彻底解决问题~~
2.NAT机制
退而求其次~~
把所有的IP地址分成两大类
内网IP: 10.* 172.16.* - 172.31.* 192.168.*
外网IP: 剩下的IP
外网IP必须是唯一的
内网IP则可以重复出现~~(尤其是在不同的局域网中)
内网设备要访问外网,会给他分配一个外网IP
但是这个外网设备IP不是这个设备独占的,而是这个内网中所有的设备都共用这一个外网IP~~
一个外网IP代表了一系列的设备~
现实世界,是通过 1 + 2解决IP不够用的问题~~(动态IP + NAT机制)
正因为NAT机制,导致咱们自己的个人电脑处在内网中,不能直接被外部访问.
所以之前写的udp echo server,得部署到云服务器上(拥有外网IP),大家才能访问~~
在NAT背景下如何通信?
外网设备 -> 外网设备,不需要任何NAT,直接就能通信
内网设备 -> 其他内网设备,不允许
外网设备 -> 内网设备,不允许
内网设备 -> 外网设备,对应的内网设备的路由器,触发NAT机制进行IP替换,此时就会给这个网络数据报的源IP替换成路由器自己的IP(此时一个外网IP就能代表一大批内网中的设备)
3.IPv6协议
从根本上解决了IP不够的问题
IPv4是传统的IP协议,使用4个字节,32位来表示IP地址
IPv6是更新一些的IP协议,使用16个字节,128位来表示IP地址~~
IPv6与IPv4并不兼容~
IP地址分成两个部分,网络号和主机号
网络号:标识网段,保证相互连接的两个网段具有不同的标识
主机号:标识主机,同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号
标识网段,标识一个局域网
标识主机,标识了一个局域网内部的主机~
一个路由器有两个IP地址,分别在不同的局域网中
一个路由器的作用就是把两个局域网连起来~~
WAN口IP
LAN口IP
此时我这个路由器就把两个局域网给连接到了一起.此时这两个个局域网就称为"相邻"的局域网
这俩局域网的网络号是不能重复的!!
一个IP地址中,哪个部分是网络号,哪个部分是主机号?一定是前三个字节是网络号么??
不一定的!!
对于网络号主机号的划分,主要有两种分类方式:
1.IP地址分类(ABCDE) -> 早已淹没在历史长河中,但仍然在教科书中非常活跃
2.子网掩码(真实的划分方式)
子网掩码,站在二进制的角度来看也是一个32位整数
左侧都是1,右侧都是0.(不会有1010这种混合的情况)
左侧有几个1,意思就是IP地址的那些位就是网络号!!
上述例子中 IP地址的前16位都是网络号,剩下的16位是主机号~
前缀用来区分类别.
每个类别下,网络号和主机号长度都是固定的~~
AB类~ 主机号太长了,实际上很少有这么大的局域网~
特殊的IP地址~(假定子网掩码是255.255.255.0)
主机号为0的IP 192.168.0.0 就是网络号~~局域网不应该存在某个主机,主机号是0
主机号全1 192.168.0.255 广播地址 往这个地址上发送UDP数据报,此时这个数据就会被转发给整个局域网中的所有主机~ (TCP不支持广播) 广播功能,是你在不能预先知道对方的IP是啥,需要探测的~ 这个情况下使用的~
IP为127开头的
127.*称为环回IP
环回IP对应的特殊的虚拟网卡Io
通过环回IP传输的数据,走这个虚拟网卡(这个过程没有IO操作,纯内存操作),要比一般的这种普通IP的数据传输要快~~
主机号为1. 192.168.0.1 一般作为"网关IP"
从 A => B 之间具体路线怎么走~~
互联网,存在大量冗余~ 导致 A和 B 之间存在很多种不同的路线,具体走哪条路??? 路由选择要做的事情就是这个了~
听起来,很像高德地图~
高德地图导航的时候,是站在上帝视角(高德地图的服务器,对于整个地区的地图是非常清楚的,掌握信息非常全面) -> 带权图的最短路径问题
但是网络上没这么容易,网络环境更复杂~
某个路由器,无法把整个网络环境都记录下来~
路由器只能记录周围的环境~ (也就是能知道邻居都是谁) 路由器内部使用过路由表这样的数据结构来记录邻居的信息~
实际的转发过程,是渐进式的,类似于"问路一样"逐渐接近最终目标~~
IP数据报,在进行网络转发过程中,就加一个"逐渐问路"的过程
每个路由器只能认识周围的情况,很可能问的目标,并不知道(目的IP在路由表中,没有匹配结果的)
此时就会走路由器给你指出的一条默认路径~ (路由表中的"下一跳表项" => 就会把我们的数据报指引向更上一级的路由器 => 越上一级的路由器就越见多识广~)
以太网 数据链路层/物理层
IPv4虽然不够用,但mac 地址目前还是够用的.所以mac地址可以做到每个设备都是唯一的,并且mac地址也不需要动态分配~~一般都是在网卡出厂的时候就写死的~
正因为如此,mac地址就可以作为网络上身份识别的一种有效技术手段~~
之所以ip和mac存在两套地址,是历史遗留问题~
数据链路层和网络层是被各自独立发明出来的~
导致数据链路层的大佬,不知道IP地址,发明ip的大佬不知道mac地址~
不同的数据链路层协议,对应不同的硬件物理设备,此时传输数据的上限也不一样~
以太网 => 网线~
把这个数据链路层数据帧,最大载荷长度,称为MTU~
如果承载的数据,长度超过MTU,就会在ip层进行分包(ip的一个工作就是完成这个分包/组包) 使每个分出来的结果,都能在MTU之内~
ip报头搞多份是为了接收方组包,UDP报头就不必搞多份了~~
虽然MTU有限制~ 但是没关系,IP仍然可以保证传输一个更大的数据~
虽然IP能拆包,仍然不能改变UDP最大长度是64k这样的现实~
由于拆出的这些IP数据报中只有一份UDP首部,这个首部里能够填写UDP长度的地方,也还是只有2个字节~ 64k这个限制还是存在的~
MTU的值是几??
不同的数据链路层协议,MTU不一样,只有以太网才是1500!!
ARP
RARP
是二层转发(数据链路层的转发)的必要辅助工具
交换机里有一个转发表
ARP就是用来构造转发标的机制~~
DNS,即Domain Name System,域名系统。DNS是一整套从域名映射到IP的系统。
TCP/IP中使用IP地址来确定网络上的一台主机,但是IP地址不方便记忆,且不能表达地址组织信息,于是人们发明了域名,并通过域名系统来映射域名和IP地址。
域名是一个字符串,如 www.baidu.com , hr.nowcoder.com 域名系统为一个树形结构的系统,包含多个根节点。其中:
- 根节点即为根域名服务器,最早IPv4的根域名服务器全球只有13台,IPv6在此基础上扩充了数量。
- 子节点主要由各级DNS服务器,或DNS缓存构成。
DNS域名服务器,即提供域名转换为IP地址的服务器。
浏览器、主机系统、路由器中都保存有DNS缓存。
Windows系统的DNS缓存在C:\Windows\System32\drivers\etc\hosts 文件中,Mac/Linux系统的DNS缓存在 /etc/hosts 文件中。
网络通信发送数据时,如果使用目的主机的域名,需要先通过域名解析查找到对应的IP地址: