网络: 跨主机通信.
互联网通信: 两点之间的通信路径有无数条.
- 集线器: 把一根网线差出来两根,但是同一时刻只能有一根线跑.
- 交换机: 组建局域网.
- 路由器: 本质就是将两个局域网连接起来
交换机和路由器之间的区别越来越模糊.
- 调制解调器: 使用电话线上网的时候,需要将电话线的模拟信号转化为网络的数字信号(猫).
- 光猫: 既可以插网线(传输信号使用电信号),又可以插光纤(传输信号的介质,传光信号)的设备,路由器.
IP
地址: 网络中标识一个主机的位置.- 端口号: 标识主机上的某一个应用程序.
- 协议: 约定.商量好数据传输的格式以及方式,有了协议双方才能互相理解含义.实际的做法是将一个复杂的大协议拆分为若干个相对简单的协议.
协议
协议分层
- 降低了学习和维护的成本
- 灵活地针对某一层协议进行替换
OSI
七层网络协议TCP/IP
五层(四层)网络模型
- 物理层: 约定好网络通信中基础硬件设备是啥样的(修路).比如像通信使用的网线,网口等设备都是相同规格的.把上层数据转化为二进制数据01,高低电平表示10.
- 传输层: 只关心传输的起点和终点.端到端之间的传输.
- 网络层: 主要负责路径的规划,路由选择的任务.
- 数据链路层: 针对
相邻的两个节点
具体怎样进行传输.
- 节点: 任何连入网络的交换机,路由器,电脑都可认为是节点.
- 相邻节点: 使用一根线连在一起的节点.
- 最知名协议"以太网".对由上层传下来的数据包加上以太网帧头和帧尾.
- 应用层: 应用程序,描述了传输的数据,用户要怎么进行解析和使用.
- 传输层和网络层是由OS内核实现的.提供的各种系统层面的
API
.- 应用层就是自己写的代码组成的应用程序实现的.
- 数据链路层和物理层是由硬件和驱动程序实现的.OS是通过驱动程序来控制硬件的.
- 相邻的两层之间才能进行数据交互.上层调用下层,封装好之后让下层继续封装数据.下层将解析好的数据再向上交互.
- 路由器是三层转发,其实分用/封装到网路层开始转发.(从下往上数第三层)
- 路由器需要解析分用得到源和目的
IP
是什么- 交换机是二层转发,封装/分用到数据链路层开始转发.
- 只需要下一步要发送到局域网中的哪一个主机,只需要
MAC
地址即可,所以只需要到数据链路层.
TCP/IP五层模型
应用层
- 应用层: 应用程序如何理解和使用网络中的通信数据,程序员通过编码可以自定义协议实现在应用层上的修改逻辑.
- 下四层都是系统内核/驱动程序/硬件中已经是实现好的,只能了解不能修改.
- 自定义协议主要做两件事情
- 明确协议数据要传递哪些信息(根据需求来的)
- 明确数据组织格式
- 可以按照纯文本格式,或者其他,比如
xml json html protobuffer
- 可以按照纯文本格式,或者其他,比如
- 应用层除了自定义协议之外,还有很多现成的协议,比如
HTTP/HTTPS.
传输层
虽然是系统内核实现好了,但是需要调用系统提供的socket API
接口完成网络编程.TCP UDP
.
- 端口号(
port
):- 效果就是在同一个主机上区分应用程序的.
- 同一个主机上,一个端口号不能被多个进程绑定.
- 是传输层协议的概念,
TCP UDP
报头中都包含源端口,目的端口,2个字节,16位bit描述端口号(0~65535). - 自己写程序绑定的端口号得从1024起,
0~1023
这个范围的端口号称为"知名端口号",已经被分配给一些知名的广泛使用的应用程序了.
UDP
报头
UDp
报头里就包含了一些特定的属性,携带了一些重要的信息.报头一共就8个字节,分成4个部分,每个部分是2字节.
- 一次网络通信,涉及到五元组.源
IP
,源端口,目的IP
目的端口,协议类型. UDP
报文长度也是用2个字节表示的.2个字节表示的范围是0~65535,换算单位就是64KB
.一个UDP
数据报最大只能传输64KB
的数据.- 如果要传输的数据超过
64KB
呢?- 在应用层通过代码的方式手动拆分,通过多次
UDP
发送多个数据报.本来是send
一次,现在需要发送多次才能将所有数据发送成功. - 使用
TCP
协议,因为TCP
协议并没有报文大小的限制.
- 在应用层通过代码的方式手动拆分,通过多次
- 校验和: 验证传输的数据是否是正确的
- 网络传输过程中可能会出现"比特翻转"的情况.网络传输本质上就是光/电信号,会受到物理环境的影响,电场/磁场/高能射线等.
- 将数据内容进行一系列的数学运算,得到一个比较短的结果,如果数据内容一定得到的结果一定.如果数据变了,结果一定会不同.
- 常见算法:
CRC,MD5(不可逆)
TCP
报头
有连接,面相字节流,全双工,可以通过编码和实验现象是可以了解到.
可靠传输: TCP
内部机制,和应用层编码关系并不大.
4位首部长度
TCP报文 = TCP报头(首部)+TCP载荷
一个TCP
报头,长度是可变的,不像UDP
一样固定8个字节.
4位就是4个比特位,能够表示值得范围是0~15,此时的单位不是字节,而是4字节为一个单位.
如果首部长度值是5,得到的就是20字节,就代表整个报头部分.如果首部长度值是15,表示整个TCP报头是60字节(此时的选项就是剩下的40字节).
选项option
此处的选项相当于对这个TCP报文的一些属性进行解释说明的,可有可无.
选项长度是可计算的,首部长度就描述了TCP报头到底有多长,选项之前的部分是固定长度(20个字节),首部长度-20字节得到的就是选项的长度.
保留6位(reserved)
保留的6位是为了以后的拓展来考虑的,成本降低.
如果后续TCP引入一些新的功能,就可以使用保留位.此时对于TCP的影响就会很小.
16位校验和
和UDP
报头中的校验和是一个目的.
TCP工作机制
一. 确认应答
- 可靠传输是怎样做到可靠的?
首先可靠和是否建立连接是没有关系的,其次可靠并不是100%要把消息发送到对方.
确认是否送达的方式就采用应答的机制,返回一个确认报文(ACK
),收到应答报文之后,就知道我们发送的消息已经被对方接收.
32位序号和确认序号
网络中由于"后发先至"的情况发生,在连续发送多个消息的时候,由于应答信息的消息先后顺序也发生变化,导致返回的意思发生顺序变化而导致表达意思的混乱.
- 后发先至
- 网络中数据的发送,两个主机之间路线存在多条,数据报1和2走的是不同路线时就可能存在到达报文的顺序和发送顺序是不一致的.
- 那么如何处理这种客观存在的情况呢?
给传输的数据和应答报文都进行编号即可,针对哪个报文进行回答即可解决这种问题.
发送方发送的时候对数据添加序号,接收方应答时跟上确认序号即可.只有应答报文
才有确认序号,仅仅是一个身份标识,是不需要对应答报文进行应答的,套娃了就.
- 如何标识应答报文呢? 使用的是
ACK==1
这个标志位.
TCP是面相字节流的,所以序号也是按照字节进行编号的.
- 发送的报文中,只需要包含起始序号,可以根据起始序号和报文长度获取到下一个报文的起始序号是多少.很显然,这个起始序号是递增的.
- 比如这次发送的报文是11000,下一个报文的序号就是10012000
- 应答的报文的确认序号填写的是收到报文起始序号+报文长度+1,即1001.
- 表达的含义就是:
- <1001的数据都已经确认收到了
- 发送方接下来应该从1001这个序号开始继续发送了
二. 超时重传
在确认应答的机制中如果出现丢包的现象,会发生两种情况.
- 发送的数据丢了
- 返还的
ACK
丢了
发送方是无法进行区分的,所以就都认为是丢包了,丢包是概率性事件,因此重新发送一下这个数据报,其实还是有很大概率重传成功的.所以,在丢包的时候重新发送一下数据即可.
- 到底当前这次传输,是丢包了?还是因为
ack
走的慢在路上还没到呢?- TCP直接引入一个时间阈值,发送方发送一个数据之后,在固定时间内并没有接收到
ACK
,就认为是丢包了.- 可能就会出现多次发送重复数据的情况!!! 万一是支付信息,就会发送多次,扣多次钱.
- TCP针对这种情况肯定是有去重机制的
- 首先,TCP存在接收缓冲区的存储空间,每个TCP Socket对象都有一个接收和发送缓冲区,B受到A的数据,就会放到B的接收缓冲区中,后续应用程序使用
getInputStream()
获取数据. - 缓冲区相当于一个优化的阻塞队列,可以根据数据的序号进行排序,很容易区分出来重复数据的存在.如果重复就把后来的这个数据直接丢弃即可,保证应用程序得到的一定是去重的.
- 首先,TCP存在接收缓冲区的存储空间,每个TCP Socket对象都有一个接收和发送缓冲区,B受到A的数据,就会放到B的接收缓冲区中,后续应用程序使用
- TCP针对这种情况肯定是有去重机制的
- 可能就会出现多次发送重复数据的情况!!! 万一是支付信息,就会发送多次,扣多次钱.
- TCP直接引入一个时间阈值,发送方发送一个数据之后,在固定时间内并没有接收到
- 由于去重和重新排序机制的存在,所以发送方只要发现
ACK
没到就重传即可,接受方都能处理好的. - 重传的数据可能再次丢包,所以超时重传可能重传n次.n也是有限制的,当发现发送好几次都没到,那么重传的意义就不大了,网络肯定都出现故障了.
- 重传的时候,第一次重传和第二次重传超时时间间隔是不一样的.一般来说,重传的轮次越大,超时时间间隔也就越大.超时时间变大,重传的频次就会降低.重传次数越多,重传成功的概率就越低.
可靠传输就是通过确认应答和超时重传来进行体现的.共同配合实现的TCP可靠性.确认应答描述的是传输顺利的情况,超时重传描述的是出现问题的情况.
三. 连接管理
- AB双端分别记录对方的
IP
和端口号等信息,此时的连接就建立成功了. - 此时把保存这部分信息的空间,也就是这个相关的数据结构,叫做连接(Connection).
- 断开连接就是AB把自己存储的对方的连接信息(数据结构)删除了.
- 管理:描述了连接如何创建和断开.
建立连接: 三次握手
通信双方记录对方的信息,彼此之间互相认同的过程.
本质上是四次握手,双方都需要向对方发送建立连接的请求,同时再各自向对方发送应答ACK
报文,但是中间两次握手是可以合并为一次的,所以构成了三次握手.
- 为什么要合并?不合并行不行?
必须合并,能合并还是尽量合并,毕竟网络传输的过程中的开销还是有的.比如在TCP/IP网络协议中都需要进行发送端5层的报文的封装以及接收端5层的分用的过程,更无论在物理层之间进行的光电信号的传输开销了.
- 两次握手行不行呢?
没有最后一次的确认应答,我这头已经建立了维护连接的相关数据结构,但是对端并不知晓是否建立成功,可能就没有维护好相关的数据结构,此时连接就不算建立成功.
- 三次握手作用
验证双方发送和接收能力是否正常.因为双方各自都完成了一次收发过程,验证了这个连接的全双工.对于可靠性传输起到的是辅助作用.
因为TCP是有连接的,所以需要使用三次握手这样的方式去建立连接,不能说是因为三次握手是有连接的.所以三次握手的意义如下:
-
让通信双方各自建立对对方的认同
-
验证通信双方各自的接收能力和发送能力.
-
在握手的过程中协商一些重要的参数
-
三次握手图示:
首先,客户端是主动发起连接的一方.
LISTEN: TCP状态,代表服务器此时已经准备就绪,可以处理客户端发来的连接.
ESTABLISHED:TCP状态,代表连接建立成功.
断开连接: 四次挥手
四次挥手,中间两次通常情况下不能合并,只有当两个数据发送的时机相同时才能合并.
- CLOSE_WAIT: 被动断开连接的一方.断开连接可能是客户端,也可能是服务端.
- TIME_WAIT:
- 出现在主动发起断开连接的一方,发送最后一个ACK之后,假设客户端是主动断开连接的一方,客户端处于的状态.
- 相当于四次挥手已经完成,此时的状态需要保持TCP连接状态不要立即释放,ACK只是发出去了,可能会在发送的过程中出现丢包情况.
- 在三次握手和四次挥手的过程中,也是存在超时重传的.
- 所以如果丢包了ACK,服务器端自然接收不到,就以为自己发送的FIN并没有被客户端收到,就会进行超时重传FIN数据报.
- 如果没有TIME_WAIT状态的存在,在发送ACK之后就释放了相关数据结构(连接),后续的ACK这一次挥手就无法进行了.
- 因此使用这个状态,保留一定的时间,就是为了处理最后一个ACK丢包的情况.
- TIME_WAIT等待一段时间之后,也没有受到服务器重传的FIN,此时就认为ACK已经被收到了,“没有消息就是好消息”.
- TIME_WAIT等待的时间,约定好了是2*MSL.MSL: 是指互联网上,两个节点之间数据传输消耗的
最大
时间: 拍脑门决定出来的60s,“经验值”.
四. 滑动窗口
- 可靠性和效率是冲突的,维护可靠性的同时是要消耗资源降低效率的.TCP在竭尽可能的提升效率,但是不可能比UDP更高.
- 滑动窗口本质就是降低了确认应答,等待ACK消耗的时间.
- IO操作= 传输数据+等待. 等待是占大头的,IO慢的主要原因就是因为这个等待时间的存在.
滑动窗口的本质就是不等待的批量发送一组数据,然后使用一份时间等待这一组数据的多个ACK数据.
- 把不用等待,就能直接发送数据的量叫做"窗口大小".上图中的窗口大小就是4000,代表最多无脑发送"窗口大小"数据之后就需要等待一段时间.
- 那么应该等待多长时间就应该往下发呢?
- 不是说必须等待所有的ACK都到达之后才继续往下发,而是到达了一个ACK就继续往下发送一条数据.这样就让等待的ACK始终都是4条.
收到2001的ACK时,代表10012000的数据已经被接收了,那么就可以发送50016000的内容了,上图的就相当于固定大小的窗口向右滑动了.
-
上述情况中,如果丢包了怎么办?
-
ACK丢包了
-
主要专注于确认序号的意义,代表的是这个确认序号之前的数据都被接收了.即使1001的确认ACK丢包了,但是接收到了2001的ACK,此时代表2001之前的数据都已经接收了,所以没问题.所以并不是所有的ACK都会发送,会故意少发一部分,节省系统资源.丢包是小概率事件,如果大部分都丢了说明网络已经出现了问题.
-
数据丢了
-
那么返回的ACK就会一直索要丢失的那部分数据.
-
主机A接下来接收到的几个数据都是1001,说明1001-2000的部分数据主机B没有收到,所以就要进行重传1001~2000数据. 这种只重传丢失部分的机制叫做"快重传".
-
重传之后发现再次接收到的是主机B发来的7001,说明1001-7000的数据都已经被接收了,那么主机A就开始继续发送7001~8000的数据.
-
重传之后可能发送的数据会出现顺序错乱的情况,但是TCP主机B存在接收缓冲区,应对这种接收数据顺序的错乱.会排好队之后再交给应用层读取.
如果当前传输数据密集,按照滑动窗口的方式来传输,此时按照快重传来处理丢包.
如果当前传输数据稀疏,就不按照快重传的方式处理,依然按照之前的超时重传机制处理丢包.
-
五. 流量控制
一种干预发送窗口大小的机制
.
窗口越大,发送效率越高,一份时间需要等待的ACK也就越多,但是窗口也不能无限大,就会导致:
- 完全不等待ACK,传输的可靠性就得不到保障
- 窗口太大,也会消耗大量的系统资源
- 发送速度太快,接收方处理不了,发了也白发.(只赶进度)
所以,接收方的处理能力也是发送方的一个约束条件.流量控制就是发送方根据接收方的处理能力,调整发送数据.
- 如何衡量接收方的处理能力?
直接看接收方接收缓冲区的剩余空间.每次A给B发送一次数据之后,B就计算一下接收缓冲区中剩余空间大小,然后通过ACK报文传回给A.
TCP报头结构中就存在一个16位窗口大小字段,只有在ACK位有效时才会有效,发送方A就会根据这个字段进行动态调整.
- 是否意味着窗口大小就是64KB呢?
TCP为了让窗口更大,在报头选项部分引入了窗口拓展因子,比如窗口大小已经是64kb,拓展因子为2,意思就是让64kb<<2位,变成256KB.
- 当发现接收缓冲区剩余0时,发送方A会时不时进行窗口探测报文,不携带业务数据,只是触发B的ACK报文,看看接收缓冲区最新状况.
六. 拥塞控制
流量控制和拥塞控制共同决定滑动窗口的大小.取二者较小值得到此时发送方发送窗口大小.
流量控制: 考虑的是接收方的处理能力.
拥塞控制: 考虑的是传输过程中,中间节点的处理能力. 交换机路由器等物理层传输效率.
中间节点的转发速率也是不好衡量的,但是可以通过实验的方式测试出一个合适的值.
初始阶段: 第0轮,初始大小是1个单位,发现传输顺利没丢包,就扩大窗口*2的指数型增长.
当增长速率达到阈值之后,指数增长就变成线性增长.但是增长的前提是不丢包.
当传输过程中出现丢包,说明此时发送的速率已经接近网络的极限,此时就将窗口大小缩小到很小的值,重复上述过程.
所以窗口大小一直在动态调整直到一种动态平衡.随着网络的变化而变化.
七. 延时应答
滑动窗口的关键: 滑动窗口大一点,减少等待的时间使得传输的速度快一点~在接受方受得了的情况下尽可能将窗口变得大一点.
收到数据之后不是立即返回ACK,再等待一段时间.寻求在等待的时间里面,让接受方应用层将数据读取出去,消费一波,这样接收缓冲区不就更大了嘛.下次发送方不就可以多发一点了嘛.
所以也不是每一条ACK都有必要立即返回.
八. 捎带应答
在延时应答的基础之上,提升效率的一种方式.
服务端客户端应用程序,经典的方式就是"一问一答".ACK正常是一方内核立即返回的,业务上的代码响应可能会等待一段时间.
正是因为延时应答的存在,就导致ACK本应该立即返回,此时延时到和业务响应相同时间,就可以合并为一次,让业务响应捎带刚才的ACK一并发送过去.此时的合并是存在一定概率的.
九. 粘包问题
因为是面向字节流
,在接收缓冲区中,应用层调用read()读取的时候,如果没有指定分隔符,就可能导致没有将一个完整的数据报读取到上层,导致逻辑上的错误.
只要在应用层协议定制时,明确好指定数据报之间的分隔符(数据报中不可能存在)即可,或者明确每个数据报的长度放在数据报之前即可.
十. 异常情况
传输过程中出现不可抗力:
- 进程崩溃: 对应的PCB也没了,文件描述符表也没了,相当于socket.close(),正常断开执行四次挥手,告诉对方一声.
- 主机关机: kill进程之后再关机.
- 主机掉电(拔电源)
- 网线断开
34情况是来不及挥手的
如果是接收方掉电了,发送方一直在发送数据并且等待接收方反映的ACK,但是等不到…就会进行超时重传,再怎么重传也收不到ACK,重传几次之后,最后尝试重置tcp连接,重置也会失败,最后单方面放弃.
如果是发送方掉电了,接收方发现没有数据到来,无法判断接收方是发完了还是挂了,就会先等一段时间,会周期性的向发送方发送消息,确认对方是否还在工作,“心跳包”!对方没有任何回应,说明对方不在工作状态了,就挂了.这种机制就叫做**“保活机制”**~ “摇篮系统”