应用层通信流程
单单说"应用层之间通信"显然是不严谨的,因为 TCP/IP 网络协议栈中,应用层是最上面的一层,它只负责为用户提供服务,但是真正的通信数据是要向下交给传输层进行处理的。
但是网络协议栈既然是分层的结构,其中一个原因就是为了让不同层之间忽略掉上层以及下层的存在,达到真正意义上的解耦,从而让双方看来就好像是彼此的应用层在直接通信。
网络通信需要知道对方的位置
正如在科技不那么发达的年代,两地之间通信需要通过邮寄信件的方式,那邮差小哥就必须要知道这个信件要发到哪里。如今我们可以通过网络通信,但网络中的报文也不是毫无目的地转发,一定是要知道对方的 IP 地址的。
一般的 IP 地址长相大概是 "36.152.44.95 " 这样的,这样的一串数字毫无规律可言,如果每次都只向同样的 IP 地址发送网络报文倒也可以勉强背下来。但是正如我们手机里有很多人的电话号码一样,我们不可能一生只跟一个 IP 进行网络通信,所以就需要想个办法让这些 IP 地址跟手机的通讯录一样,一一对应地加上一个方便用户记住和分辨的代名词,也就是域名。
例如,百度的域名就是:www.baidu.com
那如果我要给张三打电话,我可以在通讯录里面找到张三的姓名,也就能找到张三的手机号了。所以,如果要进行网络通信,就必须也存在一个可以根据域名找到对应的 IP 地址的东西,这个东西就是 DNS。
一个域名实际是按"点"被严格切分成倒序的层级授权链,从右往左,每一级都是上一级的"子域",直到最左边真正提供 Web 服务的主机名。
根域名:.
- 位于域名的最右侧部分,是 DNS 域名空间的最高层级,没有具体的字符标识,在域名书写中以末尾的一个"点"来表示(日常使用可省略)。实际上,www.baidu.com 后面还有一个"点",也就是 www.baidu.com.
顶级域名:.com
- 位于根域名的左顶级域名(gTLD)。
- 最初被设计出来是为了标识商业机构,现在已经没有严格使用限制,各种组织和个人都可以注册。
- 常见的顶级域名还有 .org(非营利组织)、.net(网络服务机构)、.eud(教育机构)等;国家和地区顶级域名如 .cn(中国)、.us(美国)也属于顶级域名范畴。
二级域名:baidu
- 位于顶级域名左侧,是域名的核心标识部分,也是用户或机构在域名注册时需要申请的主体。
- 百度通过注册 .com 下的 baidu 二级域名,就获得了 baidu.com 这个基础域名的使用权,就是该域名的主域名。
三级域名:www
- 位于二级域名的左侧,是主域名的子域名,由域名所有者自行定义和配置,无需额外向域名注册机构申请(其实就是域名所有者服务器上的一个目录名称)。
- www 是万维网(World Wide Web)的缩写,是互联网中约定俗成的用于标识 Web 服务的子域名,当用户访问 www.baidu.com 时,实际是请求 baidu.com 域名下的 www 子域名对应的 Web 服务器资源(也就是 www 这个目录下的内容)。
- 域名所有者可以配置任意的三级域名,比如 map.baidu.com(百度地图)、tieba.baidu.com(百度贴吧)等,均属于 baidu.com 的子域名。
既然有了域名,就要有域名服务器负责管理组织这些域名,如此才能根据域名找到对应的 IP 地址。
和域名的层级结构一样,域名服务器也是分级的,他们之间的关系类似于树状结构。

除上述三种 DNS 服务器之外,还有一种本地 DNS 服务器。DNS 请求最开始就是发给本地 DNS 服务器的,如果本地 DNS 服务器没有在自己的缓存表里找到对应的域名和 IP 之间的记录,才会进一步去访问根 DNS 服务器。
本地 DNS 服务器的地址是保存在客户端的操作系统中的

DNS 解析的具体流程如下:
- 客户端会根据 www.baidu.com 这样的域名生成一个 DNS 请求,发送给本地域名服务器。
- 本地域名服务器如果在自己的缓存记录里找到了对应的 IP 地址,则直接返回;若没有找到,则向根域名服务器发起请求。
- 根域名服务器收到请求之后,识别到对应的二级域名是 .com,则将 .com 顶级域名服务器的地址返回给本地域名服务器。
- 本地域名服务器向 .com 顶级域名服务器发起请求,.com 顶级域名服务器识别到三级域名为 baidu,则将 baidu.com 权威域名服务器的地址反馈给本地域名服务器。
- 本地域名服务器向 baidu.com 权威域名服务器发起请求,权威域名服务器中一定保存着对应的 IP 地址,所以将 IP 地址返回本地域名服务器。
- 本地域名服务器将 IP 地址返回给客户端,进而可以发起网络通信。
但是实际上并不是每一次 DNS 解析都要经过上述所有流程,因为浏览器本身和操作系统都是存在缓存的,当第一次获取到一个域名对应的 IP 地址之后,会记录在缓存中,避免每次都绕那么一大圈。如果在缓存中没有找到,还可以去本地的 hosts 文件中查找,如果都没有找到才会向本地域名服务器发起 DNS 请求。
Windows 中可以通过在终端中输入"tepad C:\Windows\System32\drivers\etc\hosts"查看 hosts 文件内容,如下:

客户端需要知道服务端处理请求的状态
客户端向服务端发起请求之后,可能会得到不同的响应。常见的就是服务端成功将客户端请求的数据返回,并由客户端浏览器渲染到页面上。当然也存在其他情况,比如用户访问的资源根本就不存在,这时服务端需要让客户端知道,"不是我不想给你返回有效数据,是因为你要的东西我根本就没有"。
服务端告诉客户端本次请求的处理结果(成功、失败、需要进一步操作等)的方式就是设置状态码。
常见的状态码类型如下:

先解释几个简单的
1XX 类状态码用来表示一种中间状态,说明服务端正在处理客户端的请求。
2XX 类状态码用来表示服务端处理请求成功,也是我们最希望看到的一种状态,具体细分如下:
- 200 OK 是最常见的成功状态码,表示没有异常出现。
- 204 No Content 与 200 OK 表达的含义相同,区别在于 204 的报文不携带服务端给客户端返回的正文数据。
- 206 Partial Content 表示此次响应给客户端的数据只是一部分,通常出现在分块下载等场景。
4XX 类状态码表示客户端发来的请求报文有误,服务端无法处理
- 400 Bad Request 只是表示是客户端的错误,但是这个错误是泛指,没有具体的原因。
- 403 Forbidden 表示客户端的请求本身没什么问题,但是客户端要访问的资源是服务器禁止访问的。
- 404 Not Found 是我们最常见的一种 4XX 类状态码,表示客户端想要访问的字眼在服务端根本就找不到。
5XX 类状态码表示服务器内部处理请求时出现错误(但实际有的服务端不想被别人知道是自己的错,所以即使出现错误也照样给客户端返回一个其他类别的状态码)
- 500 Internal Server Error 表示服务端内部错误,但是也是一个泛指的错误,没有具体的原因。
- 501 Not Implemented 表示服务端收到并顺利识别到请求,但是服务器本身并没有实现这个请求方法,所以无法处理。
- 503 Service Unavailable 表示服务端因为服务器过载、维护升级等原因暂时无法处理请求,通常是暂时的。
最后说比较重要的一类
3XX 类状态码表示用户请求资源的位置发生了变动,原来的 URL 找不到对应的资源,所以需要切换 URL 继续发起请求
- 301 Moved Permanently 永久重定向,表示请求资源的 URL 发生变更并且是永久的,此时客户端浏览器会缓存新的 URL,之后的请求都直接访问新地址。
- 302 Found 临时重定向,也是请求资源的 URL 发生变化,但只是暂时的,客户端浏览器并不需要缓存新的地址,只是下一次重新发起请求的时候要使用服务端返回的新的 URL。
- 304 Not Modified 客户端请求的资源没有改变,不需要把资源再传给客户端一次,让客户端继续用自己本地的缓存即可。
好的,那么问题来了,服务端要不停的收到很多客户端发来的请求,同样的资源可能被多个客户端访问,那服务端怎么知道在某一个客户端两次访问资源之间的这段时间里,资源没有被改变呢?
强制缓存:由客户端的浏览器通过上次收到的服务端响应中携带的过期时间判断自己缓存的资源是否过期,如果过期就再次向服务端发起请求,否则直接使用本地缓存的内容,主动权在客户端一方。

上述过程中一个很重要的字段就是过期时间,实际上服务端设置过期时间有两种方式。
- Cache-Control,相对时间。
- Expires,绝对时间。
Cache-Control 的设置选项更多,也就更加精密,所以一般建议使用 Cache-Control 实现强制缓存。当服务端返回的过期时间同时出现这两者时,也优先以 Cache-Control 为准。
协商缓存是指服务端再次收到客户端发来的请求时,通过判断请求的资源有没有改变,或者请求的时间有没有超过服务端上次设置的时间,来决定是重新返回资源还是告知客户端继续使用本地缓存,主动权在服务端一方。

注:服务端第一次返回时,上次修改时间字段为 Last-Modified,客户端再次发起请求时,携带的上次修改时间字段为 If-Modified-Since。 这种协商缓存也是通过时间字段判断资源是否改变
但是仅凭时间判断是有漏洞的
- 文件包括文件属性和文件内容,如果不修改内容,只修改属性的情况下,文件的最后修改时间也是会变化的。
- 有的修改操作极快,快到可以在一秒以内完成,而 If-Modified-Since 是以秒为单位的,故而识别不到快速的修改操作。
所以与其通过对比时间,不如给每一次修改后的资源都分配一个版本号 Etag,只要资源被修改,Etag 就一定会发生变化。

当服务端返回的字段中同时出现 Last-Modified 和 Etag 字段时,无疑是 Etag 的优先级更高。
客户端的请求类型不是单一的
HTTP 根据客户端不同的需求,将请求类型划分成不同的方法字段

上述方法对应的说明,都是由 RFC 规范来定义的。日常使用中,极大部分请求方法都是 GET 和 POST,两者的区别在于:
GET:
- RFC 规定从服务器获取指定资源
- 参数写在 URL 中,且必须是 ASCII 字符,且浏览器对 URL 的长度是有限制的
- 浏览器可以将 GET 获得的资源进行缓存,也可以保存为书签
POST:
- RFC 规定根据请求正文数据对指定的资源做出处理
- 请求携带数据一般写在请求正文中,可以是任何类型的数据,浏览器也不会对正文大小作出限制
- POST 请求不会被浏览器缓存,也不能保存为书签
关于URL传参和正文传参: 虽然 URL 传参是直接把参数信息写入浏览器地址搜索框中,正文传参并不会直接把参数显示出来。但是不能说正文传参就比 URL 传参要安全,因为说白了 HTTP 中两者都是非加密的明文传输,都是"裸奔"状态,别人抓个包就啥都看到了。
一个常见的问题:GET 和 POST 方法能保证安全和幂等性吗?
安全指的是一个请求不会破坏服务器上的资源,幂等指的是多次执行同样的操作,所得到的结果也是相同的。如果是完全按照 RFC 规范使用方法的话,GET 请求只是对服务器资源进行只读操作,所以多次请求得到的资源是相同的,也就是幂等的,且服务器上的资源也是安全的;POST 请求会对服务器资源做出修改,所以是不安全的,且多次修改得到的结果可能是不同的,所以也不是幂等的。
但是并不是所有人每时每刻都是完全按照 RFC 规范来使用 HTTP 方法的,比如我在进行本地测试的时候,所有增删查改的业务,我都是用 postman 发送 POST 请求~
请求应答式通信
请求应答式通信(Request-Response Communication)是一种双向、同步或异步的网络通信模式,核心逻辑是:一方(请求方)主动发起服务请求,另一方(应答方)接收请求并处理后,向请求方返回明确的响应结果。
说人话就是,只有请求方主动发送一个请求,应答方才能在处理之后返回一个应答。下一次再通信也是同样的过程,且每一次通信都是独立的,不和之前的历史通信相关联。
但是 HTTP 通信是要先建立连接的,这个过程是会有软硬件资源的消耗的,而且如果每次通信都重新建立连接,再把之前发过的内容重新发一遍,无疑是做了无用功。所以 HTTP 其实是可以设置保持长连接的,通信双方可以确定所有的请求都得到了应答之后再把连接断开,但是这是 HTTP1.1 及其之后的版本才支持的。
通信双方需要顺利获取并解析对方发来的数据
实际上,HTTP 通信时,双方的接收缓冲区中收到的都是一系列的二进制数据。但是接收缓冲区的大小不可能刚好等于一次通信数据的大小,况且每次通信收到的数据量都是不一样的,也就意味着缓冲区中可能同时有多次通信的数据。但是二进制的连续数据,怎么准确把每一次通信的数据精准分割呢?
在 HTTP 报头当中添加一个正文部分的长度字段,每次读取到一个 HTTP 报头部分时,往后读取正文长度大小的数据,就是一次通信的有效正文数据。
有时服务端把资源响应给客户端时需要进行压缩,但是压缩方法是多种多样的,这时需要客户端先告知服务端自己可以接受哪些压缩方法,服务端再按照相应的方法进行压缩响应。
同样的,响应的数据也会有各种编码格式,也需要客户端在发送请求时,顺道带上自己支持的编码格式,以供服务端可以响应回客户端能看懂的数据格式。
无状态协议
无状态协议(Stateless Protocol)是指通信双方在交互过程中,协议本身不保存任何上下文信息的网络协议。简单来说:服务器不会记住任何与客户端相关的历史请求记录,每一次请求都被视为独立的、全新的请求。
HTTTP 就是最典型的无状态协议。
但是我们自己电脑上的浏览器并不是"没有记忆的",因为当我关机再启动的时候,再次访问掘金官网,依然不需要我重新登录账号。HTTP 还是那个 HTTP,不同的是,为了避免用户频繁向服务端证明自己"良民"的身份,服务端可以在第一次身份认证成功之后,返回客户端一个"好人卡",保存在浏览器端,这样下次"见卡如见人",也就不需要每次都验证身份。这张卡我们也是可以看到的,它有个可爱的名字叫 Cookie。

实际上这个 Cookie 保存的就是用户访问每一个网站时使用的用户信息。大概流程就是:
- 客户端发送给服务端的请求头部中制定要服务端给自己返回一个 Cookie
- 服务端先验证客户端身份,验证成功后处理业务,并生成 Cookie 和响应数据一起返回客户端
- 客户端将 Cookie 信息保存在自己的浏览器中
- 下次再次向同样的服务端发送请求就带上 Cookie
- 服务端只需要用 Cookie 验证身份即可
但是毕竟 Cookie 中保存的直接就是用户的个人信息,如果被有心人通过非法手段盗取 Cookie,就可以以我们的身份访问我们访问过的网站。 这时服务器就站出来了,它说:"既然你客户端防盗窃的措施没有那么完善,那你的信息还是保存在我这里吧,但是也为了不用每次都验证你的信息,还是需要给你返回一个好人卡,但是这个好人卡不再是你的信息,只是一个与我内部维护起来的客户端信息一一对应的一个标识,方便你也方便我。" 于是就有了 Session ID。

那我问你,那我问你,Cookie 中保存用户信息的时候可以被盗取,那你换成 Session ID 就没事了吗?我把 Session ID 盗了,不照样可以用你的身份访问你访问的网站吗?
可以是可以的,因为网络的世界里,根本就不存在绝对的安全。只是 Session ID 保存的毕竟不再是用户的个人信息了,也就说明用户信息维护的工作从客户端变成服务端了,也就不至于让用户信息经常在纷繁复杂的网络世界里裸奔。并且有一个约定俗成的衡量是否安全的标准:如果用某种手段获取某个客户端的私密信息所消耗的成本大于获取私密信息成功后获得的利益,那么就认为是安全的。毕竟很少有人愿意"吃力不讨好",但是服务端也是有避免在这种极少数可能性的预防措施的。
- IP 验证: 服务端会留意你每次访问时所在的 IP,如果发现你几次访问时 IP 不同,就有理由怀疑你被"盗号"了,就会提醒你注意账号信息是否泄露,并再次要求你输入账号和密码以验证身份。
- 高权限操作再次验证身份: 比如你要转移一笔巨款的时候,往往会以人脸识别等方式再次确定你就是你。
- Session ID过期时间: 同一个 Session ID 并不是永久有效的,可能每过一段时间就会要求你再次验证身份以生成新的 Session ID。
HTTP 协议格式
上面所介绍的所有字段,都是要在 HTTP 协议中体现出来的,而这些内容都是要按照规范组织起来的,不能杂乱无章地直接丢给通信的对端。
HTTP 请求报文格式:

HTTP 响应报文格式:

其中,响应报头和请求报头中的键值对,就是上文中说的那些诸如要设置的字段,这里做一个汇总:

而空行就是用来分割报头和正文的,当你读取到一个空行时就该意识到后面的就是正文部分的内容了,结合报头当中出现的 Content-Length 字段就可以向后读取固定长度的数据,就是完整的正文。
再次理解 HTTP 协议
HTTP(HyperText Transfer Protocol),即超文本传输协议,是一种基于 TCP/IP 协议的应用层协议,核心作用是规范客户端(如浏览器、APP)和服务端之间超文本数据传输规则,是万维网(WWW)通信的基础。
核心关键词拆解
- 超文本(HyperText): 指超越纯文本的数据格式,不仅包含文字,还可以是图片、音频、视频、超链接、HTML 页面等。
- 传输(Transfer): 定义了客户端与服务端之间的数据请求-响应流程,以及数据的传输格式、编码方式等。
- 协议(protocol): 客户端和服务端必须共同遵守的通信规则,双方按统一格式封装、解析报文,才能完成交互。
HTTP 的核心特性
- 无状态: 协议本身不记录客户端的会话状态,每次请求都是独立的,服务端无法直接识别连续请求是否来自同一客户端(需通过 Cookie、Session 等机制补充状态)。
- 请求 - 响应模式: 由客户端主动发起请求,服务端接收请求后处理并返回响应,一问一答是固定交互流程。
- 灵活的可扩展性: 支持通过自定义报头(Headers)扩展功能,比如缓存机制、跨域访问、身份认证等。
- 明文传输: 明文以纯文本形式传输,安全性较低。