一、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
其中,第一行表示使用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
。
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请求
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 65535
Socket 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 65535
Socket 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
处理。