一)应用层协议简介:根据需求明确要传输的信息,明确要传输的数据格式;
应用层协议:这个协议,实际上是和程序员打交道最多的协议了
1)其它四层都是操作系统,驱动,硬件实现好了的,咱们是不需要管
2)应用层:当我们继续进行写应用程序的时候,尤其是应用程序负责网络通信的时候,往往是需要自定义应用层协议的,我们要根据需求,明确要进行传递的信息,我们还要明确传递的数据格式;
3)应用层这里面,最重要的事情,就是说自己实现一个应用层协议,比较简单也是在工作中经常做的事情;
FTP,SSH,TELNET,DNS(域名解析的协议)
1)使用现成的应用层协议来进行开发
2)程序员自己约定一个协议,来进行开发,只要客户端和服务器自己进行开发的;例如我在网页上面给服务器发送了一个请求,请求中包含着一个链接,然后服务器给浏览器返回了一个响应,这个响应就是一个网页;在这个过程中,使用的协议就是HTTP协议这时就可以
使用自定义协议,来约定好请求是什么格式,响应是什么格式,客户端和服务器就可以按照这样的约定来进行开发了;3)总而言之,自定义协议,其实是一个很简单的事情,只需要约定请求和响应的详细格式即可,越详细越好,要把各种细节都能够交代到,能够更好的表示当前的信息,咱们也可以自己来约定好格式,也可以基于xml,Json来约定数据格式,甚至还可以通过一些其他的二进制的方式来进行约定数据
1)例如我们要提供一个新的需求,要求在外卖软件的首页,显示一个优惠活动,用户参与活动,就可以参加抢红包的操作;
2)客户端:修改界面,可以显示优惠活动的详情;
3)服务器:修改后台逻辑,针对啥样的用户可以参加优惠,具体怎么做可以领到红包,红包金额是多少;
4)当的客户端启动的时候<就要向服务器进行查询,当前用户是否可以进行参加活动,服务器就要返回是或者否
5)这个时候在开发功能之前,就要约定好,客户端发的查询请求的功能是怎么样的(带有用户的身份信息),在约定好服务器返回的响应是什么样的(1表示可以表示能参与,0表示不能参与,true表示能参与,false表示不能参与)
1)假设现在自己实现一个功能叫做获取用户的订单历史(这个订单历史在数据库里面,我们要从服务器操作数据库来拿,这样的功能就会涉及到前后端的一个交互过程),咱们的前端和后端就是通过网络来进行交互的;
2)在这个交互的过程中我们就需要约定好,前端发送什么样子的数据,后端要返回什么样子的数据,本质上来说就是在进行规划请求和响应之间的信息,以及传数据的格式;
咱们进行查历史订单,查找谁的订单,以及查找哪一个时间段的订单,以及数据太多,你要不要截取一部分;
1)我们前端发送的请求类似于:
{
用户的ID,
查询的起始时间,
查询的结束时间,
显示的条数;
}
2)我们后端返回的响应:
2.1)查询是否成功?如果失败,原因都有哪些?
2.2)返回一个订单数组:
数组里面的每一个元素:
{
商品名,
商品单价,
商品数量,
店铺名称,
总的金额
}
像上面的过程就相当于是设计应用层协议,咱们设计应用层协议,本质上就是做两个工作
1)明确要传输的信息
2)明确数据的组织格式,请求的格式是啥样子的,响应是啥样子的
1)当前我们只是给出了一种可能的格式,此处我们这里面的数据的格式,也是可以进行随心所欲的约定的
2)相比之下我们只需要让我们的客户端和服务器都按照一定的格式来进行交互就可以了;
3)上面的这一种设计十分不好,咱们的输出传输效率比较低,这也就说明了是运行效率低;
4)咱们的这个代码可读性也不好,本身开发效率也比较低,正因为如此,大佬们发明了一些比较好的协议的模板,让我们向上套;
XML,JSON,protobuffer,前两种可读性比较好,但是运行效率不高,咱们的第三种可读性不好,但是运行效率比较高;
1)咱们的XML就是一种老牌的数据格式了,虽然现在也在用,但是越来越少了,咱们的这些标签本身占据的空间已经超过了数据本身所占用的空间,也就说引入了更多的辅助信息,我们程序的效率就会受到很大的影响,对于一个服务器程序来说最贵硬件资源就是网络带宽,对于XML来说,虽然提高了可读性,但是又引入了太多的辅助信息,比如说标签名字,这些辅助信息所进行占用的空间甚至已经超过了数据本身的一个空间,因为在XML里面要经常表示这些辅助信息,在传输相同的网络数据的时候,这就会导致传输相同数目的请求的时候,占用的网络带宽是更高的,如果说带宽固定,那么相同时间内能够传输的请求数目就是更少的,所以说我们程序的效率就会收到很大的影响;
2)咱们现在看这个XML标签,标签名就是key,标签值就是value,就构成了一个键值对,通过这些标签,就更好的体现了我们数据的可读性,尤其是哪一个部分代表是什么意思,非常一目了然,咱们的一个服务器程序最贵的硬件资源就是网络带宽,因此XML现在已经很少作为应用层协议的设计模板了,现在使用XML主要是为了做配置文件;
3)JSON的传输效率虽然要比XML要好一些,但是还是要多进行传递一些冗余信息,就是Key的名字,但是Json格式的数据也是有非常大的缺点,尤其是在进行表示数组的时候,这里面的Key是在不断重复的这也会占用大量的带宽资源但是咱们的JSON是一种最重要的设计模式
4)因此咱们的protobuffer应运而生,这是一种二进制格式的数据,在这种格式的数据里面,不会再包含上面的key的名字了,而是通过顺序或者是一些特殊的符号,来进行区分每一个字段的含义,同时在通过一个IDL文件,来进行描述这个数据格式(来进行描述每一个部分是啥意思),IDL只是起到一个辅助性开发的效果,并不会真正的进行传输,传输的只是二进制的纯粹的数据,似乎传输效率高了,但是开发效率低,可能是把对应的文字换成了对应的二进制数据,咱们通过二进制的数据对这里面的内容重新地进行了编排,甚至有可能做出一些数据压缩,只进行传递一些必要的信息,传输效率会更高,但是这些数据肉眼难以观察;
5)开发效率低,是包含了开发和调试,调试很不方便,如果说咱们的线上环境出现了问题,如果说咱们用JSON,出问题的请求和响应,一目了然(一看就知道哪里出现了问题),如果使用protobuffer,二进制的数据,根本没法用肉眼看,我们就需要自己开发一个专门的程序来进行解析这里面的数据,来进行分析这里面的问题,这就会比较麻烦;
咱们广论开发:咱们的protobuffer里面有IDL,这个就是说咱们的protobuffer约定的一种文件格式,类似于C语言结构体的写法
class message Response{ bool ok=1; string reason=2; repeated Data data=3; }
咱们来进行设计应用层协议,是一件非常普遍的事情,也是一种并不复杂的事情;
设计应用层协议,我们要做的工作就是说:
1)要根据需求,明确传输的信息
2)要进行明确数据传输的格式,要参考现有模板,比如说JSON,XML,protobuffer
端口号的用途:标识一个进程,就可以区分当前数据要交给哪个进程来处理
1)例如在开发服务器的时候,首先会让服务器提供一个业务端口,通过这个端口,就可以提供一些广告搜索的服务(上游服务器,就可以通过这个端口来获取到广告数据)
2)其次服务器还会提供一个调试端口,在服务器运行过程中,其实会涉及到很多很多的数据,有时候为了定位一些问题,就需要查看一些内存中的数据,通过这些调试端口给服务器发送一些调试请求,于是服务器解耦可以返回一些对应的结果;3)直接拿调试器打一些断点可以吗?
如果拿调试器来进行断住程序,此时整个进程就会处于一种阻塞的状态的,这就意味着这个服务器是无法响应正常的业务请求的4)咱们的很多网络服务是进行使用非常常用,非常广泛的服务,为了更好地进行管理,我们就给这些服务分配一些专门的端口号
80---->http服务器
443---->https服务器
23 ------>ssh
23------>ftp服务器,自己的部署http服务器,就可以把他绑到80,也可以把它绑到其他转口
当一个数据包到达了网卡以后,然后自己主机上面的电脑的所有进程都是通过同一个网卡来传输数据的,可以让每一个进程分别绑定不同的端口号,此时收到的网络的数据包中也会包含一个目的端口的字段,此时就会让这些目的端口找到对应的端口号的进程,从而把数据交给对应的进程,这个过程就是由操作系统内核来完成的
5)咱们的端口号的范围就是0-65535,这里我们就需要说一些常见的知名端口号,我们把0-1024这些端口号,给进行划分出了一些具体的应用;
6)因为咱们的很多网络服务是属于非常常见,非常应用广泛的一个服务,我们为了进行更好的管理,我们就需要给这些服务分配一个专门的端口号,一方面你见到了这个端口,就知道这是什么服务,另一方面我们把常见的服务的端口分配好,然后我们再进行部署一些服务的时候,这些服务之间的端口号就不会容易冲突;
7)我们并不是强制要求,而是建议,因为咱们的80端口是http服务器,咱们的443端口是http服务器,咱们的22端口是ssh服务器,咱们的23端口是ftp服务器;
端口号:端口号是一个整数,用来区分进程;但是PID(用于进程的身份标识)也是一个整数(PCB中的一个特性),用来区分进程,为什么在网络编程中,不直接使用PID,而是直接用到端口号这样的概念呢?
端口号是固定不变的,端口号我们可以手动指定,但是PID每次进程启动之后都会发生改变,这是系统自动分配的,我们无法控制;
1)例如客户端要连接一个服务器,客户端就要先知道服务器的IP与PID,一旦服务器进行重启,PID就会发生改变
中国移动:10086--类似于端口号,这是固定不变的
转换人工:工号xxx为您服务
他就类似于PID,每次接通电话可能都不一样
同样的,两个进程无法绑定到同一个端口号
2)系统提供的原生的socket API其中有一个方法就叫做bind,功能就是把端口号和一个socket关联起来,一个进程是可以绑定多个端口的3)端口号是传输层协议的概念, TCP和UDP协议的报头中都会包含源端口和目的端口, 并且都是使用2个字节, 16bit来表示端口号, 范围也就是 0 -> 65535; 但是我们日常写的程序使用的端口号一般都是从1024开始的, 因为0 -> 1023这个范围的端口号也称为 “知名端口号/具名端口号”, 这些端口号系统已经分配给了一些知名并广泛使用的应用程序
4)这里我们并不是完全不能使用
0 -> 1023
这个范围的端口号, 只是建议使用, 虽然这些端口被分配给了特定程序, 但是这些程序是否在主机运行着, 主机上是否安装了这些程序都是不一定的, 要使用0 -> 1023
这些端口, 需要注意2点 :
- 要确定这个端口没有和程序绑在一起.
- 要拥有管理员权限.
应用层和传输层的联系:除了最上面的应用层,下面的传输层,网络层和数据链路层,物理层这四层都已经在操作系统内核,驱动程序和硬件设备已经实现好了,不需要我们去实现,传输层是紧接着应用层的一层, 虽然传输层是操作系统内核实现好了, 但是我们在写应用层代码的时候, 是要调用系统的
socket API
去完成网络编程, 所以需要我们了解这里传输层的一些关键协议UDP
和TCP
.
二)UDP协议:适用于数据量比较小的场景了,数据载荷比较小
2.1)UDP抱头:
1)传输层,是基于操作系的内核进行实现的,咱们的程序员是不需要和传输层来进行打交道的,但是传输层意义非常重大,因为进行网络编程是需要用到Socket,也就是说一旦调用了Socket代码就进入到了传输层的范畴;
2)咱们一个完整的UDP数据包=UDP报头+UDP数据载荷,UDP数据载荷就是一个完整的UDP数据包;
3)所谓的把一个应用层数据报封装成UDP数据包,其实本质上也就是说加上了8个字节特定含义的数据
4)咱们在代码中所写的端口号,就会被打包到这样的UDP数据包里面,这是我们在报头中体现的,咱们以UDP客户端为例,源端口也就是操作系统自动默认给客户端分配的端口,目的端口也就是服务器的端口已经被指定;
5)为什么端口号的范围是0-65535,在UDP数据报头中最多也就给安排了两个字节的数据
6)咱们是否可以把UDP这里面的端口改成4个字节之类的呢?
1)不可以,一方面这个东西是操作系统内核实现的,不好意思,你都拿不到windows系统源码,就改不了内核
2)第二方面,就算你把自己的主机的UDP协议给改了,那还有别人的主机呢?要想改,就把所有的世界上的主机都进行修改;
1)咱们的入口服务器会根据请求分别从广告服务器和大搜索服务器来进行获取数据,并进行拼接,最终会得到一个完整的网页返回给浏览器
2)现实就是说咱们的广告服务器和入口服务器和咱们的自然搜索结果服务器和入口服务器要进行网络的传输和交互,就需要选定一个好的应用层协议,最开始的时候用的UDP协议
3)因为最开始的时候,咱们的所进行使用的就是UDP协议,但是随着时间的推移,随着业务的发展,咱们的广告服务器所进行吐出的业务数据越来越多,更复杂;
2.2)UDP校验和:
UDP校验和:是验证网络传输过程中这个UDP数据是否是正确的,因为咱们的网络上面的传递数据的本质是光信号和电信号
1)网络上传输的数据,有可能会发生故障,网络上的数据本质都是一些0/1的bit流,这些bit流是通过光信号和电信号来表示的(高频率表示1,低频率表示0)
2)如果在传输过程中出现了比特翻转(0->1,1->0)的情况,尤其是卫星上面的数据,要考虑比特翻转和太空上宇宙射线,就比如说磁场,就会使我们的原有的一些传递的信息发生了改变肯定会有一些特殊的情况会影响到我们的数据传输质量,再举一个例子,我们本来是想要发一组连续的高频信号,因为我们遇到了一个强磁场,我们就可能会导致其中的某些高频信号变成低频信号(从0变成1),在网络传输中,我们一定会遇到一种情况,会影响到我们的网络传输质量的,本来表示0,结果变成1了,数据就变错了;
3)校验和的本质上是检验当时数据的传输是否出问题,需要尽可能识别出数据是不是错的,校验和就可以帮发现数据是否错误,但是校验的效果不够理想, 万一你的数据同时变动了两个bit位(前一个字节少1, 后一个字节多1), 就会出现内容变了, CRC没变这样的情况.
比如说我想要去超市买菜,我妈妈让我买四样菜,土豆,西红柿,白菜,酸菜;
校验和:四样菜,我们就可以根据四样菜来进行判断买菜是否正确;
1)我买完之后,发现手里面有5样菜或者3样菜,就发现我买错了;但是此时我发现手里面有4样菜,就一定买对了吗?
就是有可能是菜有猪肉,羊肉,西兰花,韭菜
其实也不一定,这是仅仅靠检验数据来进行校验的,所以校验和正确不一定保证数据正确2)校验和不一定可以百分之百的进行校验,如果校验和正确,不一定可以保证数据100%一定对,但是如果校验和不正确,这时就可以直接判断数据一定是错的;
上面的一副对联就相当于是校验和,就是基于内容来计算出来的校验和,比如说最后一条变成了小李飞刀,对联就对不上了,比如说src,md5还有sha1;
2.3)UDP是如何进行数据校验的:
第一种方式:crc机制也叫做循环冗余检验,例如现在有一行二进制数据,依次按照字节为单位,将这些字节进行累加,如果说咱们的校验和数据出错,那么直接丢弃
short sum; for(byte b:数组) { sum=sum+b; } 这是如果出现溢出,溢出的数据就不要了
1)传输数据的时候,就会把数据和src校验和一起传输给目标,接收方同时收到了src校验和数据,接收方就需要验证一下当前的数据是否是正确的(在网络传输中可能会出现比特反转的情况,接收方再按照同样的算法,再根据数据部分进行校验和,把这个新计算的结果和收到的src校验和进行对比,看看结果是不是一致的;
2)本质上来说就是,在发送之前,针对要发送的数据算一遍src,接收到之后再次算一遍src,把这个新算出的结果与发送过来的src的结果进行对比看看他们俩相同,如果出现了比特翻转接收到的内容,和发送的内容就不一样了,此时算出的两份src就大概率是不会相同了;其实还是有一定的概率,两个不同的数据,算出的src是一致的,这个概率整体上来说是比较小的
3)md5算法,这种算法的应用场景非常多,用来做校验和,只是其中的一个场景;本质上是一个非对称的哈希算法;
3.1)首先回顾一下哈希表,它的核心是一个数组,可以做到O1复杂度的增删改查,借助了数组下标的随机访问能力,通过key进行哈希算法,转化成一个数组下标;
3.2)哈希算法有很多种,针对整数的哈希算法,都是比较简单的;md5算法本质上是针对数据进行一系列的数学变换,md5算法的特性:定长,分散,不可逆
1)定长:无论输入的字符串长度有多长,得到的md5的值都是固定的字符串的长度(32位),4个字节64位, 八个字节(128位)
2)分散:只要输入的字符串改变了一点点得到的md5的值都会差别巨大,如果两个字符串的md5的值一致,基本上是不可能的;
3)不可逆:给定源字符串,很容易算出md5的值,但是对于md5的值,很难算出字符串
可以作为哈希算法,也可以作为校验和,key是MD5,value是原始字符串4)例如在银行里面设置密码,后台的工作人员可不可以看到呢?
密码原文,小键盘输入,程序读取到这个密码之后,就可以通过MD5进行加密,然后数据库里面存的也是密文,由于MD5不可逆的特性,程序员无法感知
2.4)UDP总结:
1)无连接:知道对端的IP地址+端口号就可以直接进行传输,不需要建立连接;
2)不可靠:没有任何的安全机制,咱们的发送端发送数据包之后,如果说因为网络故障导致无法收到信息,UDP协议层也不会给应用层传递任何错误信息;
3)面向数据包:咱们的应用层交给UDP多么长的报文,UDP会原样进行发送数据,既不会拆分数据,也不会合并数据,如果说发送端一次发送100个字节,那么接收端也必须一次接受100个字节,而不是说循环接收10次,一次接受10个字节;
4)缓冲区:咱们的UDP只有接收缓冲区,没有发送缓冲区,咱们的UDP没有真正意义上面的发送缓冲区,我们进行发送的数据会直接交给操作系统的内核,由内核直接把数据交给网络层协议来完成后续的传输动作,但是咱们的UDP具有接收缓冲区,但是这个接收缓冲区并不能保证咱们的收到的UDP数据包的顺序和发送UDP数据包的顺序是一致的,如果缓冲区满了,再次到达的UDP数据就会被丢弃;
5)大小受限,咱们的UDP首部长度中有一个是16位的最大长度,也就是2个字节,也就是说一个UDP数据包的最大传输的数据范围是64K;
三)TCP协议:
1)16位源端口和16位目的端口:表示这个进程从哪里来到哪里去;
2)32位序号和32位确认序号:进行编号保证可靠性
3)4位首部长度:这里面是表示四个比特位,本质上来说就是表示TCP报头的长度,TCP的报头是在不断变长的,不像UDP一样就是固定8个字节,此处的表示该TCP头部有多少个32个bit位,都多少个四个字节,4位表示4个比特位,也就是说TCP头部的最大长度就是说4*15=60个比特位;
4)16位紧急指针标识哪些数据是紧急数据;
5)16位窗口大小标识滑动窗口的大小
6)16位校验和表示发送端填充,CRC校验,如果说接收端校验不通过,那么就认为数据有问题,此处的校验和不光包含TCP首部,还包含着TCP数据部分;
7)URG:表示紧急指针是否有效;
ACK:表示确认序号是否有效;
PSH:表示提醒接收端应用程序立刻从TCP接收缓冲区里面把数据读走;
RST:表示对方要求重新建立连接,我们把表示为RST标识的称之为复位报文段;
SYN:表示请求建立连接,我们把SYN标识的段叫做请求同步报文段;
FIN:表示本段要进行关闭了,我们把表示为FIN结束标识的段称之为结束报文段;
8)保留6位:保留位的意思就是说现在还不进行使用,但是保不齐以后会进行使用,也就是说咱们是为了未来的升级留点空间;
9)选项:主要是说这个选项,可以有,也可以没有,可以有一个,可以有多个;
1)TCP头部中的最后一个选项字段是可变长的可选信息,这一部分最多包含40个字节,因为TCP头部长度最多就是60个字节,还包括前面讨论的20个字节;
2)最重要的是窗口扩大因子;
可靠性:不是说发送出去的数据100%就能够接收到,我发出去的数据,到底对方收没收到,我心里有数,我可以知道我发送的数据成功还是失败(接通电话的时候,对方有反应,就知道可靠不可靠);
1)换一种角度来说,客户端发送出去的数据,客户端知道对方是接收到发送的数据,还是知道对方没有接收到我的数据;
2)发送出去之后就不管了,也不知道你有没有收到,这就叫做不可靠传输;
3)接收方收到消息之后,给发送方返回一个应答报文ACK,表示自己已经收到了;
1)确认应答机制(保证可靠传输的核心)(正常情况下,数据传输的可靠性)
咱们虽然在TCP代码上面看不出来,但是这个确实是TCP中最可靠的机制,引入TCP的关键原因就是为了可靠传输,因为TCP是有连接的,所以在客户端发送数据给接受方了,接收方就应该返回一个应答报文,当发送方收到了这个应答报文后就认为对方已经收到了,我打电话的时候,我进行在电话里面说话,对方说嗯嗯,我才表示已经收到了;
1)但是网络上的传输,顺序是不确定的,因此就不可以单纯的通过收到数据的顺序来判断和确定逻辑,你所进行发送的两个数据包,是不一定走同一条路的,先发后到;
2)例如:我给我的女神发送消息
第一次发送请求,你在吗?女神回复说在,这就是一个可靠传输
第二次发送了两个请求:
请求一:我想请你吃个麻辣烫,请求二:做我女朋友吧
3)这是我发送了两条消息,一个是好呀好呀,另一个是滚,由于网络延迟,我不知道他的回答是是针对我的那条请求来回答的,出现了后发先置的情况,我并不知道它的这两个响应报文分别对应着哪一次请求,可能这个吃麻辣烫是滚,对做我女朋友是好呀好呀;
1)这时女朋友和我可以来个序号(编号):
我:请求1:女神你陪我吃麻辣烫好吗?请求2:女神,你做我女朋友好吗?(序号)
女神作出回应:针对请求一是好呀好呀,针对请求二,是滚,确认序号,我们如果说使用编号,就不会再出现数据顺序混乱的情况了,让他们彼此之间对应的上;
2)确认序号表示当前这个应答报文是针对哪一个消息进行的确认应答,TCP是依靠字节来进行编号,通过序号来进行表示当前发送的是哪一条消息,通过确认序号来进行表示针对哪一条消息来进行回应;