网络原理
重要性:
网络原理知识
1.工作中非常重要的理论知识,尤其是正在调试一些bug的时候.
2.面试中非常重要的考点.
3.学习中非常关键的难点.
网络原理这里,主要给大家介绍, TCP/IP协议 这里的关键协议.
按照这里的这四层,分别进行介绍(物理层不涉及)
应用层
是和程序猿打交道最多的层,很多时候写代码,都是涉及到应用层协议的!!!
需要我们自定义一个应用层协议,而自定义应用层协议难度不大.
自定义协议的原因:
当前的软件(应用程序)要解决的业务场景,是错综复杂的,不同的公司,有不同的业务,不同的业务有不同的流程……业务复杂了,使用程序来解决这个复杂的业务,程序也就复杂了.因此,很难有一个通用的协议,满足所有的业务需求!!!
注: 业务是一个公司的命脉,而技术不是!!!业务才是关键,技术只是辅助业务来进行的,得有复杂的业务,才能有对应牛逼的技术.追求去大厂的原因之一,就是大厂的业务才会更复杂,涉及到的技术才会更有难度 => 以后工作了,在公司中,既需要学习技术,又需要学习业务,学习技术的时候,千万不要脱离对应的业务背景.
怎样去自定义协议:
1. 结合需求,分析清楚,请求响应(客户端/服务器之间)要传递哪些信息.
例子: 点外卖.
比如,查看外卖列表.
请求: 你自己当前的位置,你的身份信息…….你的身份信息: 根据你的身份,进行一些智能化的推荐. 智能化推荐就是当下“人工智能”领域,比较成熟的一个商业化的体现.
响应: 一个列表,列表中要有商家信息(名称,图片,距离/位置,商家简介……)
再比如,点击某个具体的店铺,要展示这个店铺里都有什么吃的.
请求: 店铺的名字/ID
响应: 一个列表.列表中要有食品的信息(名称,图片,价格,简介,口味…….)
需求分析,就是软件开发中最重要的环节,没有之一.
2. 明确传递的信息以什么样的格式来组织.
可选的方案是很多的.最简单,最朴素的方式: 简单文本的方式.
请求: 约定请求是一行文本,字段之间使用‘’;‘’来分割.
用户id;地址\n
响应: 也使用文本格式.响应有多行,每一行代表一个商家列表的结果.
商家名字;商家的图片地址;商家简介;商家地址\n
商家名字;商家的图片地址;商家简介;商家地址\n
商家名字;商家的图片地址;商家简介;商家地址\n
\n
进行上述约定,目的就是为了让客户端和服务器之间,能够步调一致.约定好协议的具体格式内容之后,客户端就能够按照这个格式构造数据并发送,服务器按照这个格式解析处理……
约定的协议的内容(传递的信息)是和业务相关性特别大的,但是协议的数据组织的格式(传递的格式),和业务相关性没那么大,因此,针对上述这个组织数据的格式,业界有一些大佬发明了一些比较通过,也被我们广泛使用的数据格式.
典型的用来组织数据的格式:
- XML 标签化的数据组织方式,使用标签来表示键值对,以及树形结构.
请求: 开始标签: 结束标签:
开始标签和结束标签,需要成对出现,标签中间的部分就是标签的内容.内容可以是数字,可以是字符串,也可以是嵌套放别的标签.
请求:
<request><userId>出家人(小和尚)</userId><position>重庆市巴南区重庆理工大学</position>
</request>
注: 可以认为HTML是XML的特殊情况.
响应:
<response><shops><shop><name>某家凉皮1</name><position>xxxx.xxx.xxx</position><description>这家凉皮挺好吃的1</description> </shop><shop><name>某家凉皮2</name><position>xxxx.xxx.xxx</position><description>这家凉皮挺好吃的2</description> </shop></shops>
</response>
- json 在2010年以前,XML以前很流行,后来因为它比较繁琐,json就出现了.json这个格式,最初是出自于js这个语言.
请求:
{userId:出家人(小和尚),position:"重庆市巴南区重庆理工大学",
}
响应:
{shops:[{name:"某家凉皮1",description:"这家凉皮挺好吃的1",position:"xxxx.xxx.xxx"}{name:"某家凉皮2",description:"这家凉皮挺好吃的2",position:"xxxx.xxx.xxx"}]
}
json这个格式推广开了之后,就逐渐取代了XML的地位,现在很多地方都是使用json来组织数据的格式,XML则越来越少了.
上述XML和json都是按照文本的方式组织的
优点: 可读性好,用户不需要借助于其他的工具,肉眼就能看懂数据的含义.
缺点: 效率不高,尤其是占用较多的网络带宽. XML中要传很多标签,json中要传额外很多的key……,对于服务器来说,最贵的硬件资源,不是CPU,也不是内存,而是网络带宽.
- protobuffer
谷歌发明的,二进制的表示数据的方式.针对相关的数据信息,通过二进制的方式进行压缩表示了.
特点: 肉眼观察不了,因为是二进制的,直接用记事本打开,是乱码的,不过,它占用的空间小了,传输的带宽也就降低了.
应用层还有一些特定的协议,也是非常知名,非常广泛使用,比如,HTTP协议.
传输层
传输层虽然是操作系统内核已经实现好的,但是程序猿写代码,要调用系统提供的socket api完成网络编程.socket 就是属于传输层的部分.
端口号: mysql 默认端口号 3306.起到的效果就是区分一个主机上具体的应用程序.
要求: 在同一个主机上,一个端口号不能被多个进程绑定.
例子: 进程A绑定了3306,此时进程B也尝试绑定3306,进程B绑定操作就会失败,抛出异常了.
端口号是传输层协议的概念,TCP和UDP协议报头中都会包含源端口和目的端口.
TCP和UDP都是使用2个字节,16个bit位来表示的,所以一个端口号的取值范围: 0 -> 65535.但是我们自己写程序,绑定端口的时候,得从1024开始,而0->1023这个范围的端口,称为**“知名端口号/具名端口号”**,这些端口号是属于已经分配给一些知名的广泛使用的应用程序.
如果你写代码头铁非要指定一个1023以内的端口,怎么做:
1.先确定你使用的这个端口号确实没有程序在绑定.
2.确定你有管理员权限.
注: 1023以下的端口,也不是完全不能用,只是不建议.这些端口虽然被分配给了特定程序,但是这个程序具体在你的电脑上是否运行着,电脑上是否安装了这些程序,都是不一定的.
UDP协议
无连接: 知道对端的IP和端口号就直接进行传输,不需要建立连接.
不可靠传输: 没有任何安全机制,发送端发送数据报以后,如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息.
面向数据报: 应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并.
用UDP传输100个字节的数据: 如果发送端一次发送100个字节,那么接收端也必须一次接收100个字节,而不能循环接收10次,每次接收10个字节.
全双工: UDP的socket既能读,也能写,这个概念叫做全双工.
UDP 协议报文结构
UDP就会把载荷数据(就是通过UDP socket,也就是 send 方法拿来的数据的基础上再在前面**拼接**(相当于字符串拼接,不过此处是二进制,不是文本的)上一个字节的报头.UDP的报头里就包含了一些特定的属性,其中就携带了一些重要的信息.
不同的协议,功能不同,报头中带有的属性信息就不同.对于UDP来说,报头一共就是8个字节,分成4个部分(每个部分,2个字节)
一次网络通信,涉及到五元组: 源IP,源端口,目的IP,目的端口,协议类型.
UDP报文长度,也是2个字节表示的,2个字节表示的范围是0->65535,换算单位,64KB,一个UDP数据报,最多只能传输64KB的数据.64KB的数据是非常非常小的.
问题来了,如果应用层数据报,超过了64KB,怎么办?
方案1.就需要在应用层,通过代码的方式针对应用层数据进行手动的分包,拆成多个包通过多个UDP数据报进行传输.就是send一次不行,就send多次.例子: 搬家,东西太多了,一辆车装不下,多装几车.
方案2.不用UDP,换成TCP(TCP就没有这样的限制).例子:搬家时,直接交个大点的货车,一次运完就行了.例子:搬家,直接叫个大点的货车,一次运完就行了.
灵魂拷问,这两个方案哪种好?
答: 方案2更好, 理由: 使用方案1的话,就得写很多代码,还得进行很多测试,还得处理很多bug,工作量变多了,工作时间变多了,多加班,幸福感降低了.
校验和: 作用是验证传输的数据是否是正确的.
背景: 网络传输的过程中,可能会收到一些干扰,在这些干扰下就可能出现“比特翻转”(1->0,0->1)的情况.网络传输,本质上就是光信号/电信号,这些可能会收到一些物理环境的影响,比如电场/磁场/高能射线……
本来传输的是1111 0000 ,收到干扰之后,变成了1111 0001.而一旦数据变了,对于数据的含义可能就是致命的,典型的例子,程序中经常使用1表示某个功能开启,0表示关闭.本来网络数据报是想开启功能,结果因为翻转,导致变成了关闭了.
这样的现象是客观存在的,不可避免的,我们能做的就是,及时识别出,当前的数据是否出现问题,因此就引入了校验和来进行鉴别.
具体做法就是针对一系列的数学运算,得到一个比较短的结果.如果数据内容一定,得到的校验和结果就一定.如果数据内容变了,得到的校验和也就变了.
特殊情况: 数据传输过程中出错了,但是计算得到的校验和和之前的校验和恰好一样,上图中的“要发射导弹”=> 0xaabb,”不要发射导弹”=> 0xaabb,这种情况是存在的,不过工程上出现这种情况的概率极小,忽略不计了.
总结: 如果内容相同,得到的校验和一定相同[肯定的],校验和相同,原始内容,不一定相同[存在的小概率事件],但是在工程实践中,这种情况忽略不计了,一般认为校验和相同,原始内容也相同.
注: 针对网络传输的数据来说,生成校验和的算法有很多种,其中比较知名的几个:
-
CRC
循环冗余校验,简单粗暴,把数据的每个字节,循环往上累加,如果累加溢出了,高位就不要了.
优点:容易计算
缺点:校验效果不是特别理想,在你的数据同时变动了两个bit位(前一个字节比后一个字节多1这种),就会出现内容变了,CRC没变这样的情况. -
MD5
MD5不是简单相加,有一系列的公式,来进行更复杂的数学运算.
MAD5算法的特点:
1.定长,无论原始数据多长,得到的MD5都是固定长度.
2.冲突概率小,原始数据哪怕只变动一个地方,算出来的MD5值都会差别很大,让MD5的结果更分散了.
3.不可逆.通过原始数据计算MD5很容易,但是通过MD5还原成原始数据(找到那个数据生成了这个MD5)就很难,理论上是不可实现的(计算量极大).注: 由于MD5这样的特点,就让MD5的作用更多了:1.校验和 2.作为计算hash值的方式. 3.加密
-
SHA1
TCP协议
TCP是更重要的协议,相当于UDP是复杂不少
特点: 有连接,可靠传输,面向字节流,全双工.
TCP协议段格式
4位首都长度: 一个TCP报头,长度是可变的,不是像UDP一样固定8个字节.
选项: option(选项) => optional(可选的,可有可无的), 此处的选项相当于对这个TCP报文的属性进行解释说明的.
因此,首部长度就描述了TCP报头具体多长,另外,选项之前的部分是固定长度(20字节),首部长度减去20字节得到的就是选项部分的长度.
首部长度是4位,即4个bit位,4bit => 0-15. 但是首部长度的单位不是字节,而是4字节.
举例1:如果首部长度值是5,表示整个TCP报头是20字节(相当于没有选项).
举例2:如果首部长度值是15,表示整个TCP报头是60字节(选项相当于是40字节).
保留(6位) ~~ reserved
背景:在C语言中,就有一类单词,叫做关键字,除了关键字之外,还有一类单词,叫做保留字(意思就是现在还没用,但是不知道以后要不要用,所以在这占个位置,让别人先不用).
此处TCPd保留的6位,也是为了以后的扩展来考虑的.
原因:对于网络协议来说,想扩展升级,是一件成本极高的事情!
以UDP为例,报文长度是2字节,因此一个包最大64KB,就比较小.那么现在能不能把UDP协议升级一下,让它支持更大的长度? 比如把报文长度使用4字节表示.
现实是理论可行,但是实际操作成本极高.因为这不是一个技术问题,而是涉及到营销问题.全世界上百亿能上网的计算机/路由器/手机等等…它们这个设备的操作系统里,就是支持2字节长度UDP协议.即要想进行升级,就得让这些设备的操作系统都能够同步升级成4字节报文长度的UDP协议.
那怎么降低升级成本了,就引入了“保留位”这个概念.如果后续引入了一些新的功能,就可以使用这些保留位字段,此时,对于TCP本来的报头结构的影响是比较的,老的设备即使不升级也更容易兼容.
注:我们进行程序开发的时候,其中一个重点考虑的事情就是可扩展性,有些功能可能暂时不需要,保不齐未来要需要.
16位校验和: 和UDP的校验和同理.
博主备注:TCP协议段格式的其他部分讲解,在文章后面再进行讲解.
TCP内部的工作机制
TCP是一个复杂的协议,里面有很多东西机制,我们目前讨论的是10个比较核心的机制.
一. 确认应答
TCP的可靠传输: 可靠不是说,发送方100%把信息发给接收方.而是尽力而为,尽可能把数据传输过去,同时,如果还是传输不过去,至少能知道传输不过去.
确认应答就是实现可靠传输的最核心机制!
A给B发了个消息,B收到之后就会返回一个应答报文(ACK,acknowledge的缩写),此时,A收到应答之后,就知道刚才发的数据已经顺利到达B了.
生活中随处可见这样的应答机制,比如,打电话就相当于可靠传输.
注意,由于网络上存在“后发先至”的这个情况,收到的消息是可能存在变数的!!!
原因: 两个主机之间,一方面线路存在多条,数据报1和数据报2走的都是不同路线;另一方面数据报1的转发路径上的路由器/交换机 和 数据报2的转发路径上的路由器/交换机也不一样,有的转发速率快,有的转发速率慢……所以,这两个数据报的到达顺序,就更存在变数了.
结论: 网络后发先至这个现象是客观存在的,也是无法避免的,因此应答报文到达的顺序也是可能发生变动的.此时,就需要考虑规避这种顺序错乱带来的歧义.
解决: 如何解决上述的后发先至问题了? 办法很简单,就是给传输的数据,和应答报文都进行编号,就可以了.当我们引入序号了之后,此时就不怕顺序乱了,即使顺序上乱了,也可以通过序号来区分当前应答报文是针对那个数据进行的,TCP协议段格式中的序号和确认序号概念就引出来了.
序号和确认序号: 任何一条数据(包括应答报文)都是有序号的,确认序号,则是只有应答报文才有,普通报文确认序号字段里的值无意义.
这一条报文是否是应答报文,取决于ACK这个标志位,这个标志位为1,表示是应答报文,如果为0,表示不是应答报文.
TCP是面向字节流的,TCP的序号是按照字节来编码的.
TCP的字节的序号是依次累加的,这个依次累加的过程对于后一条数据来说,启始字节的序号就是上一个数据的最后一个字节的序号,每一个TCP数据报报头填写的序号只需要填写TCP数据的头一个字节的序号即可.TCP在知道了头一个字节的序号后,再根据TCP报文的长度,就很容易知道每个字节的序号.
确认序号的取值,则是收到的数据的最后一个字节的序号 + 1.
小结: TCP 可靠传输的能力,最主要就是通过应答机制来保证的,通过应答报文,就可以让发送方清楚的知道传输是否成功.进一步的引入了序号和确认序号,针对多组数据进行详细的区分.
博主推荐: <<图解TCP/IP>>这本书,对于你了解网络知识很有帮助.
二.超时重传
在数据传输过程中,是存在丢包现象的.
丢包的两种情况:
1.发的数据丢了.
2.返回的ack丢了.
这两种情况,在发送方视角来看,它都没有收到ack,区分不了是哪种情况,于是,这两种情况,会一视同仁都被认为是丢包了.此时,TCP并没有躺平,还是要尽力挽救的.
因为丢包是一个概率性事件,在通常情况下,丢包的概率是比较小的.因此,重新发一下数据报,还是有很大的概率成功传输的.所以,TCP就引入了重传机制,在丢包的时候,就重新再发一次同样的数据.
问题来了: 如何判断当前这次传输,是丢包了?还是ack走得慢,在路上呢?
解决: TCP直接引入了一个时间阈值.发送方在发了一个数据之后,就会等待ACK,并在此时开始计时.如果在时间阈值之内,也没有收到ACK,就不管此时ACK是不是还在路上,还是彻底丢了
超时重传,超过一定时间,还没响应,就重新传输.
注: 这个超时时间,是可配置的,并且不同系统上面的默认值都可能存在差别.
问题: 由于重传,就可能导致接受方,收到了多次的重复消息.如果当前数据是扣款请求,那就会导致重复计费,因此TCP对于这种重复数据的传输,是有特殊处理的,就是去重.
具体解决措施: TCP存在一个“接受缓冲区”这样的存储空间(接受方操作系统内核里的一段内存).每一个TCP的socket对象,都有一个接受缓冲区(还有一个发送缓冲区,但目前不考虑这个).
主机B收到主机A的数据,其实是B的网卡读到数据了,然后把这个数据放到B的对应的socket的接受缓冲区.后续应用程序使用getInputStream
,进一步的使用read
从接受缓冲区来读数据.
个人理解: 可以把接受缓冲区想象成是一个阻塞队列(更严格来说,可以理解是一个优先级队列,或者是有序队列),根据数据的序号,TCP很容易识别当前接受缓冲区里的这两条数据是否是重复的.如果是重复的,则把后来的这份数据直接丢弃了,保证了应用程序调用read
读取到的数据一定是不重复的.
小结: 由于去重和重新排序机制的存在,放送方只要发现ACK没有按时到达,就会重传数据.即使重复了,即使顺序乱了,都没事,接收方都能很好的处理好(去重和排序都依赖TCP报头的序号).
特殊现象: 重传的数据是有可能又丢包了,因此超时重传是可能重传N次的(N不是一个很大的数字).重传多次还失败,此时,继续重传就意义不大了.
解释说明: 假设一次传输的丢包概率是10%(这已经是一个很大的数字了),传输成功概率是90% .
如果第一次传输丢包,第二次重传也丢包了,概率是多少? 10%* 10%=> 1% , 如果是三次都丢了呢? 那是 0.1%.
连续重传都丢包,此时的概率原则上讲,是非常低的!!
如果这个情况真的出现了,只能说明,此时丢包的概率远远不只10% => 你当前的网络出现重大故障.
因此,重传达到一定次数的时候,就不会再继续重传,会认为网路出现故障.接下来TCP会尝试重置连接(相当于断开重连一样),如果重置还是失败彻底断开连接了.具体重传几次N,也是可配置的,就不讲解了.
注: 重传的时候,第一次重传和第二次重传,超时时间间隔,还不一样,一般来说,重传的次数越大,超时时间间隔就越大.超时时间变大,重传的频率降低,即可知重传的次数越多,说明重传成功的概率就越小了,此时重传的太快也是白费系统资源.
总结: 可靠传输是TCP最核心的部分,TCP的可靠传输就是通过确认应答 + 超时重传来进行的.其中确认应答描述的是传输顺利的情况,超时重传描述的是传输出现问题的情况.这两者相互配合,共同支撑整体的TCP可靠性.
三.连接管理
了解连接(Connection): 比如,现实中的婚姻,就是把两个人(两个家庭)连接到一起了.如果两个人新娘和新郎办完了酒席,举行完了婚礼仪式,包括入了洞房,这算是结婚完成了吗? 答案是不算的,真正意义的结婚,是领这个结婚证!!!
领到的结婚证,一式两份(红色封皮),这两份的内容几乎一模一样,新娘拿的一份,记录她的老公是谁,新郎拿的一份,记录她妻子是谁,此时,连接就建立完成了!!!
TCP建立连接,两台主机A和B,主机A就需要一个空间存储主机B的IP和端口(记录他的老婆是谁),主机B也就需要一个空间存储主机A的IP和端口(记录她的老公是谁).当两部分信息都被维护好了之后,此时连接就好了,这时我们就把保存这部分信息的这个空间(数据结构)也称为连接(Connection).进一步,断开连接的概念就是A和B把自己存储的连接信息(存储信息)删了,连接就是断开了.
管理: 就是描述连接如何创建,如何断开.
博主笔注:该篇博客并未完结,随着博主的学习,该篇博客也会随之更新的.