一、HTTP协议

在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP,所以:
- HTML是一种用来定义网页的文本,会HTML,就可以编写网页;
- HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。
HTTP协议是一个基于TCP协议之上的请求-响应协议,它非常简单,比如:我们先使用Chrome浏览器查看百度首页,然后选择View - Developer - Inspect Elements就可以看到HTML,切换到Network,重新加载页面,可以看到浏览器发出的每一个请求和响应:

对于Browser来说,请求页面的流程如下:
- 与服务器建立TCP连接;
- 发送HTTP请求;
- 收取HTTP响应,然后把网页在浏览器中显示出来。
1-1、HTTP请求格式

请求头和请求体之间用空行分隔。
浏览器发送的HTTP请求如下:
GET / HTTP/1.1(请求行)
Host: www.sina.com.cn(请求头)
User-Agent: Mozilla/5.0 xxx
Accept: */*
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8
Accept-Encoding: gzip, deflate, br, zstd
Referer: http://localhost/login.html
Connection: keep-alive......(请求体)
其中,第一行表示使用GET请求获取路径为/的资源,并使用HTTP/1.1协议;
从第二行开始,每行都是以Header: Value形式表示的HTTP头,比较常用的HTTP Header包括:
- Host: 表示请求的主机名,因为一个服务器上可能运行着多个网站,因此,Host表示浏览器正在请求的域名;
- User-Agent: 标识客户端本身,例如Chrome浏览器的标识类似
Mozilla/5.0 ... Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...) like Gecko;(可以在服务器端获取该头的信息,解决浏览器的兼容性问题。) - Accept:表示浏览器能接收的资源类型,如
text/*,image/*或者*/*表示所有; - Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
- Accept-Encoding:表示浏览器可以支持的压缩类型,例如
gzip, deflate, br; - Referer:用来告诉服务器:当前请求是从哪个页面跳转过来的。
什么是 Referer?
Referer是 HTTP 请求头字段之一,用来告诉服务器:当前请求是从哪个页面跳转过来的。
示例:
你在网页 A.html 上点击了一个链接跳转到 B.html,这时候浏览器会发送一个请求给服务器,在请求头中包含:
Referer: https://www.example.com/A.html
表示:我是从 A.html 来访问 B.html 的。
Referer 的主要作用:
| 用途 | 说明 |
|---|---|
| 统计来源 | 网站可以通过 Referer 统计用户是从哪个页面或网站过来的(比如百度、谷歌、微博等) |
| 防盗链 | 图片/资源服务器可以检查 Referer,只允许来自特定网站的请求 |
| 安全检查 | 某些敏感操作(比如表单提交)可以通过判断 Referer 是否来自本站页面,防止 CSRF 攻击 |
| 用户行为分析 | 帮助分析用户访问路径和行为流程 |

安全与隐私问题
-
Referer会暴露用户的上一个访问页面的 URL,有可能包含敏感信息(比如搜索词、用户ID、token 等)。 -
因此现在很多浏览器支持通过 Referrer Policy 控制
Referer的行为。
🔐Referrer-Policy 示例:
你可以在网页中通过 <meta> 或 HTTP 响应头设置:
<meta name="referrer" content="no-referrer" />
或者 HTTP 响应头:
Referrer-Policy: no-referrer
| 策略 | 说明 |
|---|---|
no-referrer | 不发送任何 Referer |
origin | 只发送源信息(比如 https://example.com) |
strict-origin-when-cross-origin | 安全性高的浏览器默认值(跨站只发送 origin,同源发送完整 URL) |
HTTP/1.0 和 HTTP/1.1 的主要区别:
1、HTTP/1.0:每次请求都建立一个新的连接
-
短连接(non-persistent connection):
-
客户端每发送一次请求,就要与服务器建立一次新的 TCP 连接。
-
服务器响应完请求之后,立即关闭连接。
-
如果客户端要再发送请求,只能重新建立 TCP 连接。
-
这种方式的问题:
每次请求都要三次握手,效率低下。(建立连接,发送数据,关闭连接)
建立和断开连接的开销大,影响性能,特别是在加载多个资源(如网页中的图片、CSS、JS)时更明显。
2、HTTP/1.1:默认使用连接复用(持久连接)
-
长连接(persistent connection):
-
默认启用了
Connection: keep-alive。 -
一个 TCP 连接在一次请求/响应之后不会立即关闭,可以复用这个连接来传输后续的请求数据。
-
多个 HTTP 请求和响应可以复用一个 TCP 连接(串行发送,不能并发)。
-
优点:
减少连接的建立与关闭次数。
提升性能和效率,特别适合网页资源加载。
简要对比总结表:
| 特性 | HTTP/1.0 | HTTP/1.1 |
|---|---|---|
| 默认连接类型 | 短连接(每次请求新建连接) | 长连接(连接复用) |
| 是否支持 Keep-Alive | 需要客户端手动设置 | 默认支持(Connection: keep-alive) |
| 性能 | 多请求时效率低,资源开销大 | 高效,适合加载多个资源 |
GET请求 VS POST请求

请求方式:
HTTP协议有7种请求方式,常用的有2种:
- GET:
- 请求参数在请求行中,在 url 后。
- 请求的 url 长度有限制的
- 不太安全
- POST:
- 请求参数在请求体中
- 请求的 url 长度没有限制的
- 相对安全
1-2、HTTP响应

服务器的响应如下:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 21932
Content-Encoding: gzip
Cache-Control: max-age=300<html>...网页数据...
服务器响应的第一行总是版本号+空格+数字+空格+文本,数字表示响应代码,其中:2xx表示成功,3xx表示重定向,4xx表示客户端引发的错误,5xx表示服务器端引发的错误。
从第二行开始,服务器每一行均返回一个HTTP头。服务器经常返回的HTTP Header包括:
- Content-Type:表示该响应内容的类型,例如
text/html,image/jpeg; - Content-Length:表示该响应内容的长度(字节数);
- Content-Encoding:表示该响应压缩算法,例如
gzip; - Cache-Control:指示客户端应如何缓存,例如
max-age=300表示可以最多缓存300秒。
HTTP请求和响应都由HTTP Header和HTTP Body构成,其中HTTP Header每行都以\r\n结束。如果遇到两个连续的\r\n,那么后面就是HTTP Body。浏览器读取HTTP Body,并根据Header信息中指示的Content-Type、Content-Encoding等解压后显示网页、图像或其他内容。
通常浏览器获取的第一个资源是HTML网页,在网页中,如果嵌入了JavaScript、CSS、图片、视频等其他资源,浏览器会根据资源的URL再次向服务器请求对应的资源。
HTTP响应状态码
数字是给程序识别,文本则是给开发者调试使用的。
HTTP 的响应状态码(HTTP Status Code)是服务器在响应客户端请求时返回的 3 位数字,用于表示响应的结果。
HTTP 状态码分为 5 大类,根据第一位数字来区分:
| 分类 | 数字范围 | 含义 |
|---|---|---|
| 1xx | 100–199 | 信息性:请求已接收,继续处理 |
| 2xx | 200–299 | ✅ 成功:请求成功被处理 |
| 3xx | 300–399 | 🔁 重定向:需要进一步操作 |
| 4xx | 400–499 | ❌ 客户端错误:请求有问题 |
| 5xx | 500–599 | 💥 服务器错误:服务器出错 |
1、常见状态码详细讲解:
📘 1xx 信息性
-
100 Continue:继续发送请求的其余部分(常用于大文件上传前探测)。
-
101 Switching Protocols:协议切换(如 HTTP → WebSocket)。
🟢 2xx 成功
-
200 OK:请求成功(最常见)。
-
201 Created:资源已成功创建(如 POST 创建数据)。
-
204 No Content:请求成功但无返回内容(如删除成功后)。
🔄 3xx 重定向
-
301 Moved Permanently:永久重定向。
-
302 Found:临时重定向(原请求方法仍保留)。(浏览器会自动重新访问)
-
303 See Other:建议用 GET 获取新地址资源。
-
304 Not Modified:缓存命中,资源未更改。(让浏览器直接用自己本地的缓存)
❌ 4xx 客户端错误
-
400 Bad Request:请求语法错误。
-
401 Unauthorized:未认证(需要登录/Token)。
-
403 Forbidden:服务器拒绝访问,权限不足。
-
404 Not Found:找不到资源(最常见)。
-
405 Method Not Allowed:请求方法不被允许(如 应该用GET请求,却用了POST请求)。
🔥 5xx 服务器错误
-
500 Internal Server Error:服务器内部出错。
-
502 Bad Gateway:网关错误(如 nginx 连接不上后端)。
-
503 Service Unavailable:服务器暂时不可用(可能是维护或过载)。
-
504 Gateway Timeout:网关超时。
前端调试 Ajax 请求或 RESTful 接口时,状态码是判断结果的第一参考。
1-3、HTTP协议的特点

二、Socket
2-1、什么是 Socket?
Socket 就像是“电话插口”或者“插座”,用来连接两台电脑(或手机等设备),让它们之间可以通过网络 传递信息。
举个例子:你和朋友打电话
想象你和朋友打电话聊天,需要满足几个条件:
-
你俩都有电话(设备)。
-
电话都插在电话线(网络)上。
-
有电话号码(IP + 端口号)来找到对方。
-
一个人拨号(客户端),一个人接听(服务器端)。
-
连通之后,就可以说话(收发数据)了。
这个过程就是 Socket 通信的简化版!
2-2、Socket 的基本概念
| 概念 | 通俗解释 |
|---|---|
| IP 地址 | 就像你家的地址,用来找到哪台设备 |
| 端口号 | 就像你家不同的房间编号,用来找具体的“应用”(application) |
| 客户端(Client) | 打电话的人 |
| 服务器(Server) | 接电话的人 |
| 套接字(Socket) | 插口,用来建立连接,收发数据 |
| 连接 | 电话通了,双方可以交谈 |
| 数据传输 | 双方说话/听话的过程(发送/接收) |
📢【注意】:
1、一个客户端一个socket
2、客户端有几个socket,服务器就需要对应数量的socket!
2-3、一个简单流程(服务器和客户端的单向通行)
1. 服务端干什么?
public class SocketServer {public static void main(String[] args) throws Exception {System.out.println("Server is started...");ServerSocket ss = new ServerSocket(9999);System.out.println("Server is waiting for client request...");// 服务器只有accept the requests from the client,才会创建socket!Socket s = ss.accept();System.out.println("client connected");BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));String msg = br.readLine();System.out.println("client data: " + msg);}}
【注意】:
服务器只有accept the requests from the client,才会创建socket!
2. 客户端干什么?
public class SocketClient {public static void main(String[] args) throws Exception {String ip = "localhost";int port = 9999; // 0-1023 to 65535Socket so = new Socket(ip, port);String s = "navin ready";OutputStreamWriter ow = new OutputStreamWriter(so.getOutputStream());PrintWriter out = new PrintWriter(ow);out.write(s);out.flush();}}
结果展示:

2-4、一个简单流程(服务器和客户端的双向通行)
客户端:
public class SocketClient {public static void main(String[] args) throws Exception {String ip = "localhost";int port = 9999; // 0-1023 to 65535Socket so = new Socket(ip, port);String s = "navin ready";OutputStreamWriter ow = new OutputStreamWriter(so.getOutputStream());PrintWriter out = new PrintWriter(ow);out.println(s); // 自动添加换行符out.flush();System.out.println("C : data is send to server...");// 客户端接收数据BufferedReader br = new BufferedReader(new InputStreamReader(so.getInputStream()));String ServerMsg = br.readLine();System.out.println("C : data from server " + ServerMsg);}}
服务器端:
public class SocketServer {public static void main(String[] args) throws Exception {System.out.println("S : Server is started...");ServerSocket ss = new ServerSocket(9999);System.out.println("S : Server is waiting for client request...");// 服务器只有accept the requests from the client,才会创建socket!Socket s = ss.accept();System.out.println("S : client connected");BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));// readLine() 会一直等到读到一个换行符(\n),才返回读取的内容。否则它会一直阻塞在这行,不往下走。String msg = br.readLine();System.out.println("S : client data: " + msg);// 服务器接受数据OutputStreamWriter ow = new OutputStreamWriter(s.getOutputStream());PrintWriter out = new PrintWriter(ow);String nickName = msg.substring(0, 3);out.println(nickName);ow.flush();System.out.println("S: data send from server to client...");}}
结果:


2-5、Socket 的几个特点
-
双向通信:可以发送和接收
-
阻塞模型(默认):比如
accept()会等客户端连上才继续,否则一直阻塞。 -
如果一方断了,连接就会中断
-
使用流(InputStream / OutputStream)读写数据。
2-6、用在哪里?
Socket 技术是很多网络应用的基础,比如:
-
网页浏览器访问服务器(底层是 HTTP over Socket)
-
微信聊天
-
文件传输
-
网络游戏
总结一句话:
Socket 是让两台设备通过网络“对话”的工具,就像打电话一样,一端拨号(客户端),一端接听(服务端),打通之后可以收发数据。
2-7、简单的双向聊天系统
-
一个 服务器端(Server)
-
一个 客户端(Client)
实现功能如下:
-
客户端连接服务器后,双方可以互相发消息;
-
使用多线程实现服务端可以同时收和发,不阻塞;
1. 服务端代码:ChatServer.java
import java.io.*;
import java.net.*;
import java.util.Scanner;public class ChatServer {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(9999);System.out.println("🔵 Server started. Waiting for client...");Socket socket = serverSocket.accept();System.out.println("✅ Client connected.");// 接收客户端消息的线程Thread receiveThread = new Thread(() -> {try {BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String msg;while ((msg = reader.readLine()) != null) {System.out.println("👤 Client: " + msg);}} catch (IOException e) {System.out.println("❌ Client disconnected.");}});// 发送消息给客户端的线程Thread sendThread = new Thread(() -> {try {PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);Scanner scanner = new Scanner(System.in);while (true) {String msgToSend = scanner.nextLine();writer.println(msgToSend);}} catch (IOException e) {System.out.println("❌ Failed to send message.");}});receiveThread.start();sendThread.start();}
}
2. 客户端代码:ChatClient.java
import java.io.*;
import java.net.*;
import java.util.Scanner;public class ChatClient {public static void main(String[] args) throws Exception {Socket socket = new Socket("localhost", 9999);System.out.println("✅ Connected to server.");// 接收服务端消息的线程Thread receiveThread = new Thread(() -> {try {BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String msg;while ((msg = reader.readLine()) != null) {System.out.println("👤 Server: " + msg);}} catch (IOException e) {System.out.println("❌ Server disconnected.");}});// 发送消息到服务端的线程Thread sendThread = new Thread(() -> {try {PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);Scanner scanner = new Scanner(System.in);while (true) {String msgToSend = scanner.nextLine();writer.println(msgToSend);}} catch (IOException e) {System.out.println("❌ Failed to send message.");}});receiveThread.start();sendThread.start();}
}
3、使用说明
-
先运行
ChatServer.java
显示:🔵 Server started. Waiting for client... -
再运行
ChatClient.java
显示:✅ Connected to server. -
然后你可以:
-
在客户端窗口输入消息发送给服务器;
-
在服务端窗口输入消息发送给客户端;
-
双向实时通信!
-
4、为什么要用一个接受线程和一个发送线程呢?
因为 接收和发送是两个独立的、可能会阻塞的操作,放在同一个线程里会导致程序卡住,无法实现“边发边收”的实时通信。
举个例子
如果你写的是下面这样的代码(没有用线程):
void chat() {while (true) {sendMessage(); // 发送给对方receiveMessage(); // 接收对方消息}
}
发送或接收其中一个阻塞了,另一个就没法运行!
Socket 的 readLine() 是 阻塞的:
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = br.readLine(); // 如果对方没发消息,会一直卡住!
如果你把这个放在主线程里,程序就被卡死在这里了,无法再去读取你的键盘输入、也不能再发消息了。
| 功能线程 | 作用 | 是否会阻塞? | 如果阻塞会影响谁? |
|---|---|---|---|
| 接收线程 | 一直监听对方消息 | 会 | 只影响自己(接收) |
| 发送线程 | 等待你输入消息 | 可能阻塞 | 只影响自己(发送) |
分线程后,即使你一直在等输入,对方发来的消息也能实时收到 —— 这就是并发的好处。
了解了socket以后,就可以编写一个简单的HTTP服务器。 读取HTTP请求,发送响应。
HTTP目前有多个版本,1.0是早期版本,浏览器每次建立TCP连接后,只发送一个HTTP请求并接收一个HTTP响应,然后就关闭TCP连接。
由于创建TCP连接本身就需要消耗一定的时间,因此,HTTP 1.1允许浏览器和服务器在同一个TCP连接上反复发送、接收多个HTTP请求和响应,这样就大大提高了传输效率。
我们注意到HTTP协议是一个请求-响应协议,它总是发送一个请求,然后接收一个响应。能不能一次性发送多个请求,然后再接收多个响应呢?
HTTP 2.0可以支持浏览器同时发出多个请求,但每个请求需要唯一标识,服务器可以不按请求的顺序返回多个响应,由浏览器自己把收到的响应和请求对应起来。
可见,HTTP 2.0进一步提高了传输效率,因为浏览器发出一个请求后,不必等待响应,就可以继续发下一个请求。
HTTP 3.0为了进一步提高速度,将抛弃TCP协议,改为使用无需创建连接的UDP协议,目前 HTTP/3 已成为正式标准,且被主流浏览器和大公司广泛支持。
| 协议 | 底层传输 | 特点 |
|---|---|---|
| HTTP/1.1 | TCP | 每次请求一个连接,慢 |
| HTTP/2 | TCP | 支持多路复用,但头部压缩、阻塞问题 |
| HTTP/3 | UDP + QUIC | 支持 0-RTT 连接、无队头阻塞,传输更快 |
三、Socket、Servlet与Tomcat
3-1、servlet是什么
Servlet 是 Java Web 服务器上的一个小程序,用来处理客户端的请求,并返回响应。
它运行在 Tomcat 这样的 Servlet 容器中。
Servlet 的作用(你只要记住这两个)
-
接收请求(通常是 HTTP 请求)
-
生成响应(比如返回 HTML 或 JSON)
Servlet 是运行在 Web 服务器(如 Tomcat)上的 Java 类,用来处理浏览器的请求并返回数据,是 Java Web 的核心技术之一。
3-2、servlet、socket和Tomcat的关系
Servlet 是基于 Socket 实现的,它建立在 Socket 通信之上,用来处理 HTTP 协议 的请求和响应。
1、为什么已经有了 Socket,还需要 Servlet?
因为Socket 太底层,如果你不用 Servlet,Web 开发要这么写:
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
in.read(buf);
// 然后你自己去解析 HTTP 请求头、请求体...
你需要:
-
手动读字节;
-
手动解析请求头;
-
手动拼接 HTTP 响应字符串;
-
手动处理 Content-Type、Cookie、Session...
太麻烦、太容易出错、不适合开发者写业务逻辑。
Servlet 就是解决这个问题的“Java 规范”(接口)
它说:“HTTP 这个活我帮你干了,你专心写处理逻辑就行。”
2、那 Tomcat 又是干嘛的?
Servlet 是一个规范(接口),不是你能直接运行的代码。
Tomcat 就是帮你跑这些 Servlet 的软件,它负责:
-
管理 Socket;
-
管理 Servlet 生命周期(创建、调用、销毁);
-
调用你写的
doGet()/doPost(); -
维护 Session、线程池、连接池...
3、Servlet 底层是这样运作的:
浏览器发起 HTTP 请求 → 被 Tomcat 的 Socket 接收(底层是 TCP)↓
Tomcat 解析 HTTP 请求(请求行、请求头、请求体)↓
创建 HttpServletRequest 对象↓
调用 Servlet 的 doGet()/doPost() 方法↓
生成响应 → 封装成 HTTP 响应格式 → 通过 Socket 返回给客户端
也就是说,Servlet 不直接操作 Socket,而是 Tomcat 等服务器帮你用 Socket 把 HTTP 请求“翻译”好,然后交给 Servlet 来处理业务逻辑。
4、servlet处理动态请求
Servlet 主要用于处理客户端发送的“动态请求”,比如表单提交、登录、数据库查询等。
而 静态请求(如 HTML、CSS、图片)不需要 Servlet,Tomcat 等服务器可以直接处理并返回资源。
(1)、什么是“动态请求” vs “静态请求”?
| 类型 | 示例 | 是否走 Servlet? | 举例说明 |
|---|---|---|---|
| ✅ 动态请求 | /login、/query?user=tom | 需要 Servlet | 要执行 Java 代码,处理参数、查数据库等 |
| ❌ 静态请求 | /index.html、/style.css、/logo.png | 不需要 Servlet | Tomcat 直接读文件,从硬盘返回给浏览器 |
(2)、Servlet 的职责:
-
接收 HTTP 请求
-
分析请求参数(如 GET/POST 参数)
-
调用业务逻辑(比如数据库)
-
动态生成响应内容(如 HTML、JSON)
-
返回给客户端
(3)、静态资源的请求流程:
你在浏览器输入:
http://localhost:8080/static/index.html
Tomcat 会:
-
在 web 项目的
webapp/static/文件夹里找到index.html -
自动读取文件内容
-
用 HTTP 协议把文件返回给浏览器
这个过程不需要 Servlet 参与,因为没有动态处理逻辑。
(4)、配置角度(默认行为)
在 web.xml 或 Spring Boot 配置中,一般会设置哪些路径交给 Servlet 处理,哪些路径交给静态资源处理器,比如:
<servlet-mapping><url-pattern>/api/*</url-pattern> <!-- 动态请求走 Servlet -->
</servlet-mapping>
而静态资源路径(如 /static/ 或 /public/)默认直接由 Tomcat 的 DefaultServlet 处理。
3-3、小结

