网络原理
文章目录
应用层协议
一般应用层协议都是自定义的,由程序员自己设计:
- 明确传输的信息
- 明确数据的格式
上述的数据传输格式,可以随心所欲的约定,不同的应用程序里面的格式都不一定相同。但是自定义应用层协议,尤其是数据格式的定义,太灵活未必是一件好事,比如可能会影响维护。因此,应用层中就有一些特定的数据传输格式。
- HTTP应用层中最最重要的,最最常用的协议,后面额外展开
- XML比较典型的(古老)数据组织格式,通过标签的形式来组织键值对数据的
- JSON当前最流行的一种数据组织格式,相当于XML的替代品
- PROTOBUFFER一种二进制格式,用于提高传输效率。
//XML
<request>
<postion>
<longitude>经度</longitude>
<latitude>纬度</latitude>
</postion>
<preferences>
<preference>音乐</preference>
<perference>体育</perference>
</preferences>
</request>
//JSON
{
postion:{
longitude:1234,
latitude:483
},
preference:{
'音乐','体育'
}
}
JSON:首先是一个{},{}里面包含多组键值对,键值对之间用,来分隔。键和值之间用:分隔。键只能是字符串类型,值可以是字符串类型,数字,数字类型。
JSON优点:
1. 可读性强
2. 看起来整洁优美
3. 可扩展性强
缺点: 引入了额外的字符串,传输数据量变大了,消耗额外的带宽.
传输层
端到端之间的传输,重点关注的是起点到终点。
核心的协议有两个:
UDP:无连接,不可靠传输,面向数据报,全双工
TCP:有连接,可靠传输,面向字节流,全双工
UDP协议
理解协议的报文格式
UDP报文分为报头段和数据段,报头又分为四个字段:
- 源端口号
- 目的端口号
- UDP长度
- 校验和
每个字段都是二个字节一共八个字节。
UDP长度能表示的数值范围是0-65535个字节也就是64kb,当初创建UDP的时代还属于很看重存储的时代,UDP能表示的范围在当时算的上很大的了,但是计算机行业发展迅速,现在的数据动不动就几个GB,此时UDP的64kb就有点小了,当单个数据报长度接近或者是超过时,一般的策略就是换成TCP传输,因为TCP是字节流的没有长度限制。
校验和的作用是检查数据是否出错,校验的方式是CRC算法(循环冗余校验),把UDP报文中的每个字节都进行累加,加和也放到一个两个字节的数字中,加的过程溢出了也无所谓,得到的最终结果也就是校验和。接收方按照同样的规则来算一遍,来看一下是不是一样。
TCP协议
报文结构
-
源端口号
-
目的端口号(1,2和UDP一样)
-
序号
-
确认序号
-
4位首部长度
-
保留位
-
URG,ACK,PSH,RST,SYN,PIN,六位标志位
后面会讲到
TCP的基本特性
- 有连接
- 可靠传输
- 面向字节流
- 全双工
可靠传输,TCP中最最核心的特性
可靠传输
TCP可靠传输的部分机制
1. 确认应答机制
如:手机发短信(看到短信必回)
发送方:王总,今天我想请个假
如果发消息不是一个可靠传输,那么此时你就不知道消息是否发成功了,你就不知道能不能请假,此时就是不可靠传输。可靠传输指的是数据传输过去之后,发送方知道自己发没发送成功。
接收方:好的
当发送方看到接收方的回复消息,那么此时发送方就可以判定上次发送的数据是成功到达了的,如果隔了一会还没看到回复那么就可以认为上次发送的数据丢了。
这种回复机制就叫做确认应答机制,“好的”就是应答报文,也称为ack报文,六位标志位中的一种ack=>acknowledge(应答),ack=0普通报文,ack=1应答报文,确认应答机制就是TCP保证可靠传输最核心的机制!
在网络中每次发送请求走的路线可能不同,所以可能会出现“后发先至”的情况,导致应答不准确。
还是上面的例子
你:王总,今天我想请个假
你:王总,我想加薪
王总:好的
王总:不好
如果出现后发先至的情况导致“不好”,在“好的”前面到达了,你就可能会产生误会,确认应答机制为了避免这种情况就加入了序号保证不出现歧义
你:王总,今天我想请个假(1)
你:王总,我想加薪(2)
王总:好的 (针对1)
王总:不好 (针对2)
TCP结构中的序号(针对请求数据进行编号)和确认序号(只是针对ACK报文)。TCP协议是字节流传输,编号的时候也是按字节为单位进行编号的。如:报文的序号为1,报文的长度为1000,那么最后一个字节的数据的编号就是1001,TCP报文中只能存一个序号,最后一个字节的序号是通过报文长度算出来的,报文的长度是通过IP协议中算出来的,而确认应答报文中的确认序号是1001,表示的含义是,1001之前的数据我都收到了(也可以理解为继续索要从1001开始的数据)
确认应答描述的是数据包顺利到达之后,对方给个响应,但是传输的过程中,可能会丢包,那么丢包又该如何?
2.超时重传
当我发了一个消息隔了一会还没收到回复时,此时我就会重新发送消息,这就是超时重传。发送方的请求和应答报文都会丢包这时发送方分不清是谁丢了包,这两种情况都要超时重传。这两种超时重传是有区别的,如果是发送方的消息丢了,正常超时重传没啥,如果是应答丢了,再超时重传时接收方就会接到两个一模一样的消息,此时TCP就会针对相同的消息进行去重(根据序号来去重即可),保证了应用层读取数据时读到重复的数据。
超时时间如何确定呢?
一般系统里面会有一个配置项,描述超时的时间阈值,不过这个阈值并非是均等的而是逐渐变大的且也不是无限的,这样的重传重试几次之后仍然无法传输,就会尝试重置TCP连接(断开重连),如果连不上,此时就会释放连接(彻底放弃)。
3.连接管理
连接管理描述的就是TCP建立和断开连接的过程。
- 建立连接(双方建立一个相互认同的关系)
三次握手~~(本来是四次,中间两次可以合并)
为啥要建立连接,建立连接的意义是什么?
- 投石问路,检查一下当前的网络情况是否畅通(三次握手建立连接并不传输任何数据)
- 三次握手同时也是在检查通信双方的发送能力和接收能力是否正常
- 三次握手过程中也是在协商一些重要参数
三次握手中两个重点的TCP状态:1. LISTEN:服务器启动之后,绑定端口之后,表示手机开机信号良好。2.ESTABLISHED:连接建立好之后的稳定状态。
- 断开连接(双方取消相互认同的关系)
因为在三次握手中,B返回给A的ACK和SYN都是由内核触发的,也就是立即发送,都是内核触发时机也完全相同那么内核就会把两个包合并成一个。四次挥手中只是可能合并,因为四次挥手的ACK是内核收到FIN后立即触发的而FIN是应用层显示调用socket的close方法后触发的。为什么说还是有可能呢,因为TCP后面的一个机制捎带应答,可以让ACK等一下FIN,这样就可以合并了。
四次挥手中两个重要的TCP状态:1.CLOSE_WAIT:等待代码中调用close操作。2.TIME_WAIT:主动发起FIN的一方,会进入TIME_WAIT,发送方处理完最后一个ACK后还需要等待一段时间这是为了防止最后一个ACK丢包后还有机会重传,等待的时间一般是2个MSL,MSL->网络上两个位置之间传输数据消耗的最大时间。
4.滑动窗口
俗话说有得就有失,TCP为了保证可靠传输,其实是牺牲了一部分的效率的,滑动窗口就是再已经低效率的情况下,来想办法提高效率,属于是亡羊补牢了。显然之前那种一发一收的情况效率是比较低的,滑动窗口就是一次发送多条数据,把发送方的等待应答时间重叠起来就提高了效率。不过这个过程中如果产生了丢包是怎么处理的呢,丢包有两种丢包,一种是ack丢了一种是数据包直接丢了。
-
ack丢了
基于TCP序号和确认序号设计的巧妙很好的解决了这个问题,确认序号代表的意思是找发送方要序号开始后的数据或者是代表序号前的数据我已经收到了,如果前面的应答丢了但是后面有应答到了就代表了该应答前面所有的数据我都收到了,此时就不需要超时重传。
此图中的1001,2001,3001应答都丢了,但是4001应答到了,代表4001前的数据我都收到了。
- 数据包丢了
当1001-2000这个数据丢失时,开始A是不知道的他会继续传输下面的数据,但此时B返回的ack向A索要的1001后的数据。当B持续需要几次之后A就会意识到1001-2000数据丢失了,于是就会重传这段数据。B收到这段数据后由于中间的数据并没有丢失所以就相当于补齐了拼图,于是B就从7001开始索要。上述的重传过程叫做快速重传(搭配滑动窗口的超时重传)相当于高效率的超时重传,当传输的数据很多,批量传输此时就是快速重传,当传输的数据量很少如只有一条时就按照普通的超时重传。
5.流量控制
滑动窗口提到一次发送多条数据可以提高效率,那么一次究竟应该发多少数据呢是不是窗口越大效率就越搞呢,很显然不是当窗口很大时,发送方一直再发送数据,此时接收方就会处理不过来从而导致丢失一些数据,这样效率反而会降低。所以在滑动窗口的基础上,需要对发送速率做出限制,限制发送方的窗口不要太大,这就是流量控制。窗口大了也不行小了也不行那么多少合适呢,这就需要问接收方的接收能力有多大了,接收方根据自己的接收能力来反向影响发送方接下来的发送速率。接收方通过接收缓冲区来量化自己的接收速率,使用接收缓冲区的剩余空间大小来作为发送方发送速率(窗口大小)的参考值。
窗口探测周期性就会发送,它是不携带载荷的数据包只是为了询问窗口大小。
TCP报文结构中的16位窗口大小就是表示的是接收方返回的滑动窗口的大小,当ack报文是1时这个位置的数据才有效。
6.拥塞控制
经过流量控制我们可以确定接收方的接收能力来控制窗口大小,但是发送方和接收方之间往往不是直接连通的,中间还有错综复杂的网络环境而网络环境也会影响到两者之间的传输,所以我们要在网络环境较差的时候传输少量数据在网络环境较好的情况下传输大量数据。但是网络环境的好坏或者说是网络接收数据的能力是不好估量的,所以可以通过做实验的方式来验证发送速度多少合适。通过不断的增加传输速率和减少传输速率来不断的探测网络的最大传输速率,但网络环境是复杂的也不是一成不变的,所以只能反复的动态调整,达到一种动态平衡,流量控制和拥塞控制都是对发送方窗口大小的控制,一个是接收方造成的一个是网络因素造成的,一个木桶能装多水主要看最短的那个木板有多长,即发送方窗口大小由控制小的那个决定。
具体实现:TCP引入慢启动机制,先发少量数据如果不丢包就指数增长的扩大拥塞窗口(拥塞控制下的那个窗口大小),然后达到设置的阈值(初始阈值一般由系统控制)后就进行线性增长,指数增长是为了快速摸清当前网络传输承载的底线,阈值是为了防止指数增长的太快一下超过了上限。
7.延迟应答
基于流量控制的提高效率的机制,接收方等待个几十ms
再返回ack这时几十ms
就从接收缓冲区取走了一大波数据,缓冲区的剩余空间就变大了,发送方的速率就也能得到提升。
8.捎带应答
基于延时应答的基础上引入的,TCP中,只要把数据传过去了对方收到后就会立即由内核返回一个ack,而响应数据则是应用程序里负责传输的,这两时机不同所以不能一起传输。但是由于延迟应答,ack就稍等了一会这时业务上也处理好了数据要返回响应,这时就可以把两个报文合并在一起了,提高了网络利用率,减轻了计算机负担。四次挥手可能只有三次就是基于捎带应答。
9.粘包问题
面向字节流的一个问题,面向字节流都是有一个接收缓冲区的,接收方的应用程序就需要从缓冲区取数据但是不知道从哪到哪是一个完整的应用层数据包。当前解决粘包问题的方法就是在应用层进行区分,再自定义应用层协议时明确出包和包之间的边界就可以了。
- 通过分隔符,比如
xml
的分隔符就是结束标签 - 通过指定包的长度,比如
protobuffer
10.TCP中的异常处理
-
程序崩溃
进程异常退出,操作系统回收进程资源,相当于socket调用close方法,执行close触发fin报文,开始执行四次挥手过程。
-
正常关机
关机的时候会系统强制关闭所有用户进程,和上述进程崩溃类似。
-
主机掉电
1.掉电的是接收方,发送方不知道对方挂了,继续发数据
此时发的数据没有ack,发送方触发超时重传,执行几次后依然没有ack就尝试重置连接(复位报文段),依然失败后就会放弃连接
2.掉电的是发送方此时接收方就会等着
接收方也不是干等着,等了一阵之后会发送一个心跳包,如果对方还在就会返回一个心跳包,如果不返回说明心跳就遗失了,说明对方挂了,此时也会断开连接。心跳包不携带任何数据的简单包存在的意义只是判断对方是否还存在。
-
网线断开
和主机掉电一样,只是双方同时执行对应的情况。
TCP和UDP之间的对比
各自的应用场景
对可靠性有要求的场景用到TCP,非常广泛
对可靠性要求不高,同时对传输效率要求比较高的场景,比如机房内部的传输。
其它协议
传输层也不只是只有这两种协议,还有很多其他的协议。
如果使用UDP如何保证可靠传输
基于UDP在应用层实现确认应答、超时重传、引入序列号…等等
总结
TCP协议和应用层是非常接近的,写代码的时候可能经常会触及到一些TCP机制,所以我们要非常熟悉TCP协议的一些机制,而且TCP协议中实现某些机制的方法是非常巧妙的,所以学习了也可以供日后开发借鉴。
【引用文献】图解TCP/IP,本文中的部分图解分析引自该书。