找往期文章包括但不限于本期文章中不懂的知识点:
个人主页:我要学编程程(ಥ_ಥ)-CSDN博客
所属专栏:JavaEE
我们在前面简单的接触了 TCP/IP五层协议中的传输层协议,并使用 UDP协议 与 TCP协议 编写了一个简单的回显服务器 与 客户端。接下来,我们来深入学习应用层的协议。
应用层,是我们在日常开发中接触的最多的,简单理解的话,就是在编写程序时,我们自定义的传输数据的协议就属于应用层协议。因此应用层协议大多都是程序员自己定义的,自定义协议需要明确两个目标:传输的数据是啥、按照啥样的方式来组织数据。传输的数据对于不同的协议来说,都是一致的,主要就是看按照啥样的方式来组织数据。虽说组织数据的格式也是我们自己定义的,但是前辈们已经为我们提供了不少的方案,我们可以来看看。
自定义协议
我们先规定一下传输的数据是啥:A给B发送了一个你好!
行文本的方式
行文本的方式是最简单的,就是将传输的相关信息全部组织到一起。
行文本:A,你好,B
xml的方式
xml 与 HTML一样都是由标签组成的,但不同的是,xml允许自定义标签,HTML是不可以自定义标签的,都是开发者们提前定义好的(但是HTML5之后,允许自定义标签了)
xml:
<sender>A<\sender>
<content>你好<\content>
<receiver>B<\receiver>
json的方式
json 是采用 键值对 的形式来组织数据的,虽然 json 是起源于JavaScript,但是几乎所有的编程语言都支持。
json:
{
sender: "A"
content: "你好"
receiver: "B"
}
通过上面三种方式的对比,我们可以发现:
1、行文本的方式最简洁,但是可读性很差(不能站在主观的角度,对于别人来说看懂有些困难)
2、xml的方式虽然可读性很高,但是由于标签的存在导致大量冗余,从而在网络传输中会导致大量带宽会被浪费,而带宽这种资源是非常昂贵的,因此xml的方式现在根本没人使用。
3、json的方式相比xml进行了改良,冗余程度比较少,可读性也不错,但是相较于行文本还是存在冗余信息,对于带宽的消耗还是挺大的。
因此,针对上述情况大佬们又研发出了,protobuf,这种基于二进制格式来存储,不仅只存储有效的数据,而且还对有效的数据进行了压缩。例如,一个整数可能只用一个字节来存储,这就大大地节省了带宽,但可读性就变差了很多,因此现在 json 还是流行的,protobuf 只是应用于高性能的场景。
http协议
当然互联网发展到今天,不管是哪一层的协议肯定都有一些被大佬们搞出来的现成的协议。对于我们Java程序员来说,http协议是最重要的协议之一。Web开发最核心的协议:http,不管是哪个网站都是采用的http协议(https是在http的基础上进行了加密)。
http协议是典型的一问一答的模式。
例如:在浏览器中,输入某个网址,浏览器就会向该网址所对应的服务器发送一个http请求,服务器也就会返回一个http响应。
这就是一问一答的模式,不会出现:服务器收到请求,不发送响应的情况。
当然,还有 一问多答、多问一答、多问多答。
一问多答:下载大文件、多问一答:上传大文件、多问多答:远程控制(todesk、向日葵)。
学习 http 协议,首先得了解其报文格式,我们通过抓包工具来获取http数据包,然后再分析数据包的组成即可。这里的抓包工具,我们使用的是 fiddler。
这里就不介绍fiddler的下载与安装了,大家可以自行去B站上面看。
安装完运行起来之后,就可以开始进行抓包了。
请求与响应的报文格式
首先得将左侧的数据包全部清除,Ctrl + X 或者 先 Ctrl + A 再 Delete 也可以。
然后我们在打开浏览器,输入CSDN 的网址。
然后再对我们抓的请求进行 解压缩+在记事本内显示:
这里需要解压缩是因为数据包在网络中传输时,为了节省带宽都是进行了压缩的(类似protobuf的方式,如果我们直接打印是完全看不懂,是乱码的形式)。
下面是请求:
请求由四部分组成:首行、请求头、空行、正文。
1、首行:请求方法 + URL + 协议版本组成。
上述请求的请求方法是 get、URL是 https://blog.csdn.net/?spm=1003.2021.3001.4477 、协议版本是 HTTP/1.1。请求方法 与 URL 与 协议版本之间都是有空格进行分隔的。
2、请求头(header):请求头的内容很多,但都是由键值对组成,每一行都是一个键值对,键 与 值之间采用 ": + 空格" 的形式来分隔。
3、空行:是用来分隔 请求头 与正文的,也是标志了请求头的结束。
4、正文(body):有些请求是没有正文的,而有些请求时有正文的。上述请求就是没有正文的。
我们再来看响应:
响应的报文格式与请求差不多,但是内容不一样。
1、首行:协议版本 + 状态码 + 状态消息。
200 就是成功的意思,这是状态码,状态消息,就是OK。
2、响应头(header):响应头与请求头一样,都是由键值对组成的。
3、空行:和请求报文中空行的作用一致。
4、正文(body):如果我们仔细去看的话,会发现这个body其实就是一个HTML。因为我们服务器返回给浏览器的就是一个页面(响应),因此正文就会是一个HTML,如果我们是去下载的话,那就不是一个HTML了。
请求报文的主要字段
URL的组成
URL(Uniform ResourceLocator)即统一资源定位符,用于标识互联网上的资源位置,其基本组成部分如下:协议类型://域名:端口号/资源路径?查询字符串#片段标识符
https://blog.csdn.net/2301_80854132?spm=1011.2415.3001.5343
这里的 https 就是协议类型,这里的 blog.csdn.net 就是域名,域名之后到?之前全部是资源路径,?之后的就是查询字符串。
注意:
1、域名部分可能会被替换成具体的IP地址。例如,127.0.0.1;
2、其实完整的URL的域名后面需要跟着端口号,因为域名是找到服务器的主机,端口号是找到主机的某个进程。但是上述URL的域名后面并未有端口号,这是由于其使用了协议的默认端口号,因此浏览器会根据默认端口进行通信。例如,http协议的默认端口是 80, https协议的默认端口是443。当然,我们手动加上端口号也是正确的。
3、查询字符串是由键值对构成的。键值对之间使用"&"来分隔,键与值之间是使用"="来分隔。上述URL中查询字符串为:spm=1011.2415.3001.5343,"=" 前面的就是键,后面的就是值。那如果查询字符串中本身就包含了"&"、"="这些特殊的字符呢?这时就需要进行URL 编码了。urlencode:把数据的二进制内容,每个字节取出来,用十六进制表示,前面加上 %。而服务器在接收到查询字符串之后,就会对其进行解码操作,将经过URL编码的字符还原成原始字符,以便于正确处理参数。
4、"#" 后面是片段标识符,用于定位的,也叫作锚部分,例如 "http://www.example.com/page.html#section2",表示打开"page.html”文件后,直接定位到“section2”这个位置。
5、访问Web端时,协议类型+域名是必选项,其余的都是可选项。
请求方法
http的方法主要是表明这次请求是干啥的。http请求的方法有很多,但是目前主要是get占八成,post一成,其余的共占一成(put 与 delete最多)。
get方法的应用场景:获取资源
post方法 的应用场景:登录、上传文件(传输资源给服务器)
put方法 的应用场景:传输文件
delete方法的应用场景:删除文件
http协议中,get方法与 post 方法 的区别:没有本质区别,可以混着用
1、语义上的区别:
get方法是获取资源;post方法是向服务器提交数据。
2、携带数据的方式:
get方法一般没有body,因此其URL的query string中携带了请求数据;
而post方法一般是没有URL的,因此其body中携带了要提交给服务器的数据。
3、get方法所对应的请求通过建议设计成"幂等",post不是"幂等"的:
"幂等"是指对同一资源进行多次相同的请求,应该返回相同的结果。
例如,访问B站主页,使用浏览器打开多个标签页面,只是每次推送的内容不一样,但是其排版布局等都是一样的。
get方法就是上述访问B站的http请求方法;
而post则不一样,假设在使用一个手机号注册一个账号时,这一次注册成功了,但是下一次再去注册就会失败,
这两次请求所对应的响应肯定是不同的。如果设计成相同的话,肯定就不合理了。
4、由于get方法所对应的请求设计成"幂等"了,就可以允许get请求的结果被缓存,post不能:
因为get请求所返回的响应是相同的,因此可以缓存到本地,无需每次通过网络传输来获取了;反观,post请求就不能。这里就需要去了解"浏览器的缓存机制":
浏览器通过网络从服务器中加载网页(tips:访问速度:cpu>内存>硬盘>网络)通常情况下,从网络加载数据比从硬盘加载要慢。因此,浏览器开发厂商为了加快页面访问的速度,就会将页面依赖的一些静态资源缓存到硬盘上,这样后续再访问时,就不需要通过网络获取了,访问的速度也快了。也就是第一次访问服务器,需要加载这么多东西.后续再访问,就不必重新加载了。可能有小伙伴初次登录某个网站时需要加载很久,但是后续再去登录时,速度就会明显提升了,这并不是网络问题,而是浏览器的缓存机制。
get请求 与 post请求的误区:
1、post请求比get请求更安全:
因为 get请求一般是不存在body的,也就是说其是通过query string来传输数据的,当其应用于登录场景时,账号与密码就会显示在搜索框(地址栏)中。但是我们所说的"安全"是指即使这个数据被别人截获了,也不会存在泄露的情况,即加密传输,而不是明文传输。
2、get请求传输数据有长度限制:
因为 get请求一般是不存在body的,也就是说其是通过query string来传输数据的,在计算机上古时期,URL的长度有限制,也就会造成get请求传输的数据有长度限制,但是对于今天来说,这是不确定的,即使有长度限制,这个数肯定会很大。
3、get请求只能传输文本,post请求可以传输二进制:
因为 get请求一般是不存在body的,也就是说其是通过query string来传输数据的,而query string中的数据是只会被识别为文本数据,即使输入二进制数据也是没用的,但是还有一个方法:将需要传输的二进制数据先转换为文本数据,再去传输文本数据就行了。
以上三个误区均是由于 get请求一般是没有body的,只会通过query string去传输数据。因此get请求也是可以有body的,也就不一定会造成上述情况了。
下面都是 header 中的主要字段。
Host
Host,表示本次请求需要访问的服务器主机的IP地址与端口。
如果本次请求的URL中不包含端口号(使用协议的默认端口号),那么在Host中也不会包含端口号,只会有IP地址;但如果URL中不是使用默认端口号的话,那对应的Host中也会有端口号。简单理解就是,Host中的内容是截取自URL中的域名+端口号。
Content-Length
Content-Length,表示本次请求 body中的数据长度,单位是字节。
如果请求没有body的话,也就不会有Content-Length这个字段了。
那这个有什么用处呢?
http协议所组织的数据,在传输层是基于TCP实现的(版本<=2.0),而TCP协议是基于字节流的,对于TCP 来说,一个连接上可以发多个请求,服务器这边收到数据的时候就得区分一下,从哪里到哪里是一个完整的 http 请求数据,没有body 的http 请求,读到空行,就可以认为是结束了(没有Content-Length,就不会去读取空行后面的信息)。那有body 的 http 请求呢?先读取首行和 header,读到空行(因为存在Content-Length),所以就解析 header 中的 Content-Length,根据这里的值,接下来再读取固定字节的长度。
Content-Length 在一定程度上也能解决粘包问题,但不是绝对。(粘包问题后面再TCP协议中会详细解释的)
Content-Type
Content-Type,表示请求中body的数据格式,相当于提前告诉接收方如何解析body中的数据,让你提前准备好,相当于"不打没有准备的仗"。
(http请求中的body能够携带的数据种类是比较多的,例如:HTML、CSS、JS、JSON、图片等)
注意:如果请求存在body,那么header中就一定会有 Content-Length 与 Content-Type。如果不存在或者只存在一个,就会认为是非法的或者错误的http报文。
User-Agent
User-Agent里面表示了用户使用的设备的浏览器和操作系统的情况。
上图中的信息了解即可,我们只要知道U-A表示的是当前浏览器与操作系统的情况即可。
通常情况下,User-Agent 主要出现在 HTTP 请求报文中。
这是因为客户端在向服务器请求资源时,需要通过User-Agent 来告知服务器自身的情况,以便服务器做出合适的响应。
例如,服务器会根据请求的User-Agent的内容来返回不同的响应。
如果是手机,就会返回适应手机大小的网页,如果是PC就会返回适应PC端大小的网页
Referer
Referer,描述了当前页面来源于哪个页面。
如果是 直接输入URL或者点收藏栏 打开的页面是其请求报文中是没有 Referer的。
这个其实就是在告诉服务器这个客户端是从哪个页面跳转过来的,如果是广告服务器的话,就会统计。当然如果你是直接输入URL或者点击收藏栏的话,广告商就会认为你是需要的,就不需要这个Referer了。
Cookie
Cookie 是服务器通过浏览器存储在用户本地上的小块数据。它由服务器发送给浏览器(通过 Set-Cookie 返回给浏览器),并由浏览器存储在本地。
每当浏览器向同一服务器再次发送请求时,会将相应的Cookie 信息一起发送给服务器,这样服务器就可以很方便的找到用户的信息了(用户在本网站上设置的相关操作就会直接起作用,而不用再去手动设置了)。
Cookie按照域名为维度,以键值对的方式来存储。其中的内容是 程序员自定义的,最典型的应用场景:实现登录身份验证。
为什么网站需要登录,因为登录之后用户所做的一些操作才会被保存下来。
例如,你没有登录这个网站,那么你的浏览记录就不会被保存,你下次再来看的时候,就找不到了;反之,你登录过这个网站之后,就会保留你上次的信息,下次再去找的时候,就可以找到。那怎么实现登录呢?浏览器在后续请求中带上这个 Cookie,服务器就能识别出用户已经登录。
响应报文的主要字段
状态码
描述了响应结果,正确还是出错,出错是啥原因。
常见的状态码:
200 -- OK
404 -- Not Found(访问的资源没找到):IP对应着主机、端口号对应着进程、资源路径对应着需要访问的资源。404 说明资源路径错误 (访问的资源,服务器上没有,也有可能是IP与端口号错误,但这种情况比较少)
403 -- Forbidden(访问被拒绝或者说没有权限)
405 -- Method Not Allowed (方法不允许)
例如,服务器可能只允许对某个资源使用GET方法来获取数据(通过注解,后面学习),但客户端却使用了POST方法试图提交数据。此时服务器就会返回 405 状态码来告知客户端该操作不被允许。
500 -- Internal Server Error(服务器出现错误,服务器处理逻辑的代码中抛出异常,但是你没有catch 到,因此最终整个程序崩溃了,也就是我们常说的"网站挂了")
504 -- Gateway Timeout (网关超时)
当服务器作为网关或代理,在尝试完成客户端的请求时,未能及时从上游服务器(如后端服务器、应用服务器等)获取到响应,就会返回504。即客户端向代理服务器发出请求,代理服务器再向上游服务器请求数据,但在规定的时间内没有得到上游服务器的回应,所以
只能返回 504 给客户端。在服务器资源紧张的时候容易触发的,例如:学校的抢课。
302 -- Move temporarily (重定向)访问服务器A,服务器A告诉你去找B,这种一般就是服务器的域名搬迁了,你访问原来的域名,原来的域名会告诉你访问新的域名。
Content-Length 与 Content-Type 都是与 请求报文一样的。
以上就是http协议的全部内容了。接下来我们学习 https 协议。
https 协议
同样,学习一个协议就要学习其报文格式。
https = http + s,这里的"s" 是指 SSL/TLS(SSL 是专门负责加密的,而TLS是基于SSL改良的)。这两个也是应用层协议。
加密有两种方式,一种对称加密,另一种是非对称加密。
对称加密:是指 加密 与 解密 使用的是同一个密钥;
非对称加密:是 加密使用一个密钥,解密使用另一个密钥。把其中一个密钥公开出去,称之为"公钥",另一个自己保存好,称之为"私钥",因此加密与解密就会有两种方式:公钥加密,私钥解密 与 私钥加密,公钥解密。
我们先来看一下,不加密也就是明文传输的情况:
因此,我们要针对明文传输的信息进行加密措施:
那问题来了,对明文进行加密的密钥是从何而来呢?这也是通过 网络传输的。
非对称加密:
注意:
1、可能有小伙伴会好奇:公钥是怎么出现在客户端手里,而私钥是怎么出现在服务器的手里的?这是服务器会根据加密算法计算出一对公钥与私钥,公钥直接公开出去,而私钥就是自己保存好。
2、后续传输数据时,我们之所以不在采取 非对称加密的方式了。是因为 非对称加密比对称加密所付出的代价要更大,只适合于传输少量的重要信息,因此这里非对称加密就用来传输对称加密的密钥了,而后续的相关信息传输全部使用了对称加密的密钥也是安全的。
上述使用非对称加密对密钥进行加密传输看似安全,实则还有安全问题。我们来看下面这种情况:
这一招偷梁换柱用的实在是妙。将公钥偷偷换掉,这样客户端使用黑客提供的公钥再去对密钥加密,而黑客则可以通过私钥来解密从而拿到其中的密钥,最后只需要再对服务器发送使用其的公钥加密的密钥即可,最终就实现了瞒天过海,因此上面的情况还是不安全的,这种安全问题,我们称之为 "中间人攻击"。
上述情况出现的究其原因就是 "客户端不能区分密钥到底是来自黑客还是服务器",而应对这种情况,就需要我们手动地为客户端加上一对"火眼金睛"。这个火眼金睛就是证书,我们也可以看一下这个证书:
我们可以从上面的证书中看到:证书的所属对象,证书的颁发者,证书的有效期,SHA-256 指纹。前面三个对象都好理解,但最后的 SHA-256 指纹 是啥呢?SHA - 256 是一种加密哈希函数,它会对输入的数据进行处理并产生一个固定长度(256 位)的哈希值。而图中的字符数是 64 位,是因为图中表示出来的都是16进制的数,而产生的256位是二进制位数,1位16进制数 = 4个2进制数。因此最终 256位的二进制数就转换为了64位16进制的数。
了解了证书的基本信息之后,我们再来看证书是如何生成的。
当服务器搭建完成之后,就可以去生成一对密钥(私钥与公钥),然后再去申请证书:申请证书 与 我们日常生活中申请补贴类似,需要提交个人材料,然后证书的颁发机构就会将这些个人材料整合作为输入生成一个校验和,然后再针对校验和进行加密,证书的数字签名本质上就是一个加密的校验和。
客户端自身持有的公钥不是通过网络传输的,而是直接内嵌在操作系统中了,只要不是盗版的系统,都会有这个公钥的。
然后黑客强行修改证书中的部分内容,那么客户端肯定会知道,这时浏览器就会弹出一个框:
浏览器就会提示你这个网站不安全,看你是否还需要继续访问。
好啦!本期 初始JavaEE篇 —— 网络原理---应用层协议 的学习之旅 就到此结束啦!我们下一期再一起学习吧!