在上一期内容中,我们介绍了网络编程,特别是基于 TCP 和 UDP 的网络套接字编程。当时提到,在使用 TCP 时,我们通常会引入线程池来处理多个客户端连接,从而避免阻塞。然而,如果客户端数量持续增加,线程数也会不断增长,过多的线程反而会影响系统性能。这时就需要引入 I/O 多路复用 机制。
那么,什么是 I/O 多路复用呢?简单来说,它允许一个线程同时处理多个客户端的请求。例如,当一个线程正在服务某个客户端,而该客户端暂时没有发送请求(比如停留在输入阶段),该线程就可以转去处理其他客户端的任务,待原客户端发出请求后再回来继续处理。
现在,我们正式开始学习今天的新内容:HTTP。
HTTP 全称为“超文本传输协议”(HyperText Transfer Protocol),是一种广泛使用的应用层协议。通常,HTTP 是基于 TCP 协议实现的,例如 HTTP/1.0 和 HTTP/1.1;而 HTTP/3 则基于 UDP 实现。目前,最常用的版本仍是 HTTP/1.1。
应用层协议实际上可以由程序员自行定义。那么,如何设计一个自定义的应用层协议呢?
主要分为两步:
- 明确需要传输的信息内容;
- 约定信息的组织格式。
举个例子,在网购场景中,我们可能需要传输商家ID、商品图片等信息。常见的组织格式包括:
(1)行文本格式
例如直接写成:
text
1,商家241,商家店名...
这种格式在早期使用较多,但在性能、安全性和数据复杂性方面较弱,目前已基本被淘汰。
(2)XML 格式
在之前的 Maven 项目中导入依赖时,我们接触过 XML 格式。例如:
xml
<Request>
<UserId>1000</UserId>
</Request>
XML 虽然结构清晰,但标签重复较多,占用带宽较大,因此目前也较少使用。
(3)JSON 格式
JSON 是目前最常用的数据交换格式,例如:
json
{
"userId": 12,
"name": "zhangsan"
}
JSON 不仅可读性好,而且比 XML 更节省带宽,但仍存在一定的冗余信息。
(4)Protocol Buffers(Protobuf)
Protobuf 采用二进制格式,对数据进行了高效压缩,虽然可读性差,但带宽消耗更小。
HTTP 作为 Java Web 开发中最核心的协议,我们必须深入理解其工作机制。
那么,HTTP 到底是什么呢?当我们在浏览器中输入一个 URL(例如京东网址)时,浏览器会向服务器发送一个 HTTP 请求,服务器处理该请求后返回一个 HTTP 响应,浏览器解析响应内容并展示页面。HTTP 作为应用层协议,关注的是请求和响应的具体格式与语义;而 TCP/IP 作为传输层协议,则负责数据的可靠传输。
简单来说,我们输入 URL 后,浏览器发送 HTTP 请求,服务器计算后返回 HTTP 响应,浏览器再解析并展示内容。
此外,HTTP 是典型的“一对一”模式,即一个请求对应一个响应。网络中还存在其他模式,例如上传文件时的“多问一答”和下载文件时的“一问多答”。
2. HTTP 协议格式
接下来,我们具体了解 HTTP 协议的格式。这里推荐使用抓包工具 Fiddler(可在官网下载),它可以帮助我们捕获和分析 HTTP/HTTPS 请求。
使用 Fiddler 时,左侧窗口会显示所有捕获到的请求。例如,打开 DeepSeek 网站后,可以捕获到一条蓝色的 HTTP 请求。点击该请求后,右侧上方显示请求报文,下方显示响应报文。建议点击 “Raw” 查看原始报文,若字体过小,可在记事本中打开查看。
左侧的报文可能显示为不同颜色,其含义如下:
- 红色:表示请求或响应出错;
- 蓝色:表示该请求返回了一个网页;
- 绿色:表示该请求返回了 JavaScript 文件;
- 灰色:表示该响应的数据已被缓存。
Fiddler 本质上是一个代理服务器,能够清晰地记录客户端与服务器之间的通信过程,就像外卖骑手清楚用户下单内容和商家出餐情况一样。
下面,我们以搜狗请求为例,分析 HTTP 请求和响应的结构:
请求示例:
text
GET https://www.sogou.com/ HTTP/1.1
Host: www.sogou.com
...
响应示例:
text
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
我们可以归纳出 HTTP 协议的通用格式:
- 首行:包括方法、URL 和协议版本(请求)或状态码和描述(响应);
- Header(报头) :以键值对形式描述请求或响应的属性,每行一个键值对,以空行结束;
- Body(正文) :空行之后的内容,允许为空。如果 Body 不为空,则 Header 中必须包含
Content-Length字段,用于指示 Body 的长度。
为什么需要空行呢?由于 HTTP 协议没有规定报头的长度,空行作为报头和正文的分隔符,确保解析器能够正确识别两部分内容。尤其是在基于 TCP(面向字节流)的传输中,没有空行可能会导致数据读取混乱。
3. HTTP 请求(Request)
下面我们详细介绍 HTTP 请求的各个部分。
3.1 URL
URL(统一资源定位符)是互联网上资源的唯一标识符。例如,京东的 URL 为:
text
https://www.jd.com/
我们可以将其分解为以下几个部分:
- 协议名:
https: - 域名:
www.jd.com(通过 DNS 解析为 IP 地址和端口号) - 路径:位于问号之前,表示资源的层次结构
- 查询字符串:位于问号之后,以键值对形式组织,多个键值对之间用
&分隔
举个例子,假设我们想在呼和浩特新城区的“好吃麻辣烫”点餐,要求菌汤底,多加葱、少放香菜、不要辣椒,我们可以用以下 URL 表示:
text
http://呼和浩特新城区:4号路好吃麻辣烫/菌汤/香菇的?葱=多放&香菜=少放&辣椒=不要
URL 中的特殊字符(如 /、?、&)具有特定含义,如果需要在查询字符串中传输这些字符,必须进行 URL 编码(URL encode)。编码方式是将字符的二进制表示转换为十六进制,并在前面加上百分号(例如 %2F 表示 /)。不仅特殊符号,某些汉字和非 ASCII 字符也需要编码。
3.2 HTTP 方法
在 Fiddler 捕获的请求中,首行通常会包含一个方法(如 GET 或 POST),用于表示该请求的意图。常见的 HTTP 方法有以下四种:
(1)GET
GET 是最常用的 HTTP 方法,用于获取资源。例如,访问网页、获取 CSS 或 JavaScript 文件时,都会发送 GET 请求。GET 请求通常没有正文,参数通过查询字符串传递。
示例:
text
GET https://www.sogou.com/ HTTP/1.1
Host: www.sogou.com
...
(2)POST
POST 方法常用于提交数据,例如登录或上传文件。POST 请求通常包含正文,并在 Header 中指定 Content-Type 和 Content-Length。
(3)PUT 和 DELETE
PUT 和 DELETE 方法分别用于上传和删除资源,但在实际开发中使用频率较低。大多数功能可以通过 GET 和 POST 实现,不过为了语义清晰,建议按规范使用。
GET 与 POST 的区别
- 语义不同:GET 用于获取数据,POST 用于提交数据;
- 数据传输方式:GET 通常通过查询字符串传递参数,POST 通常通过正文传递;
- 幂等性:GET 请求是幂等的(多次执行结果相同),而 POST 不是;
- 缓存:GET 请求可以被缓存,POST 请求通常不可缓存。
关于安全性,有人认为 GET 参数暴露在 URL 中,不如 POST 安全。实际上,无论是 GET 还是 POST,抓包后都能看到数据,安全性取决于是否对数据进行了加密。
另外,关于传输数据大小的限制,并没有明确规定 GET 的 URL 长度或 POST 的正文长度,具体限制取决于浏览器和服务器的实现。
3.3 请求报头(Header)
报头部分采用键值对结构,常见字段包括:
(1)Host
表示服务器的地址和端口。例如:
text
Host: www.sogou.com
即使在代理或 HTTPS 加密场景下,Host 字段仍保持原始地址,不会随 URL 变化。
(2)Content-Length
表示正文的长度(单位:字节)。例如:
text
Content-Length: 18962
该字段在包含正文的请求或响应中必须存在,用于帮助接收方正确解析数据。
(3)Content-Type
表示正文数据的格式,指导接收方如何解析。常见类型包括:
text/html:HTML 页面text/css:CSS 样式表application/javascript:JavaScript 文件application/json:JSON 数据image/png、image/jpg:图片资源
只要请求或响应包含正文,就必须同时指定 Content-Length 和 Content-Type,否则报文格式错误。
(4)User-Agent
描述客户端使用的设备、浏览器和操作系统信息。例如:
text
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
该字段可用于返回不同版本的页面(如 PC 端或移动端),或针对不同浏览器提供兼容性处理。
(5)Referer
表示当前页面是从哪个页面跳转而来的。例如,从搜狗跳转到广告页面时,Referer 字段会显示搜狗的 URL。早期 HTTP 时代,运营商可能篡改 Referer 进行劫持,HTTPS 的普及有效遏制了该问题。
(6)Cookie
Cookie 是存储在客户端的会话标识,常用于用户状态保持。例如,用户登录后,服务器会通过 Set-Cookie 返回一个 Session ID,客户端后续请求携带该 Cookie,服务器即可识别用户身份。
Cookie 与 Session 的区别:
- Cookie 存储在客户端;
- Session 存储在服务器端。
举个例子,去医院看病时,医生会发放诊疗卡(相当于 Session ID),并在系统中记录患者信息(相当于 Session 对象)。患者凭诊疗卡完成各项检查,医生通过刷卡获取患者信息。类似地,浏览器通过 Cookie 携带 Session ID,服务器根据 Session ID 获取用户状态。
3.4 请求正文(Body)
正文部分的内容与 Content-Type 密切相关,常见格式包括:
(1)application/x-www-form-urlencoded
表单数据编码格式,数据被编码为键值对,适用于简单文本数据提交,不支持二进制数据,且需进行 URL 编码。
(2)multipart/form-data
混合数据类型,支持文本和二进制数据(如文件上传)。每个部分以 --boundary 开头,以 --boundary-- 结尾。
(3)application/json
以 JSON 格式传输结构化数据,支持复杂数据类型,是目前最常用的数据交换格式之一。