http

Http 是互联网应用最广泛的客户端-服务器通信协议,用于规范浏览器、移动应用等客户端与 Web 服务器之间的数据交互。

HTTP 的特点

  • 无状态协议:服务器不会记忆客户端的历史请求,每次请求都是独立的
  • 只能由客户端发起请求,服务器接收后返回响应请求,服务器不会主动向客户端发送数据
  • 通过请求方法、状态码(200、403、404 等等)、头部字段(数据信息、请求头等等)实现
  • 标准 HTTP 在传输层使用 TCP,但数据以明文形式发送,有被监听、窃取的风险;HTTPS 通过加密保护数据安全

工作流程

  1. 建立 TCP 连接,客户端通过 TCP 协议与服务器的端口建立连接(三次握手)
  2. 客户端发起 HTTP 请求,客户端向服务器发送请求报文,包含要访问的 u 资源和客户端信息
  3. 服务器解析请求报文,处理业务逻辑
  4. 服务器将处理结果封装为响应报文,返回给客户端
  5. 客户端解析响应报文,处理数据
  6. 关闭 TCP 连接

HTTP 请求报文结构

请求报文由请求行、请求头、空行、请求体四部分组成,格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 请求行:方法 + URL + 协议版本
GET /index.html HTTP/1.1

// 请求头:键值对,描述客户端信息、请求参数等
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Cookie: userId=123; theme=dark
Connection: keep-alive

// 空行:分隔请求头和请求体
(空行)

// 请求体:可选,仅POST、PUT等方法需要,存放提交的数据
username=admin&password=123 // 表单数据
// 或 JSON 数据:{"username":"admin","password":"123"}

HTTP 响应报文结构

响应报文由状态行、响应头、空行、响应体四部分组成,格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 状态行:协议版本 + 状态码 + 状态描述
HTTP/1.1 200 OK

// 响应头:键值对,描述服务器信息、响应数据属性等
Server: Nginx
Date: Wed, 15 Aug 2025 12:00:00 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1024
Set-Cookie: sessionId=abc123; HttpOnly; Max-Age=3600
Connection: keep-alive

// 空行:分隔响应头和响应体
(空行)

// 响应体:实际返回的数据(如HTML、JSON、图片二进制)
<!DOCTYPE html>
<html>
<head><title>示例页面</title></head>
<body>Hello World</body>
</html>

WebSocket

WebSocket 是一种全双工通信协议,它实现了客户端与服务器之间的持久连接和双向实时通信,服务器可以主动向客户端发送请求,彻底改变了传统 HTTP 协议 “请求 - 响应” 模式的局限性

HTTP 通信方向单一,只能由客户端发起请求,服务器无法主动向客户端推送数据,并且每次请求完成后需要关闭连接,频繁通信会产生大量握手开销。


Websocket 中客户端和服务器可同时向对方发送数据,一次握手建立连接后,保持长期活跃,避免重复连接开销;无需 HTTP 多次握手,数据传输更高效

工作流程

Websocket 独立于 HTTP 的协议,但是借助了 HTTP 完成握手,之后完全脱离 HTTP 进行通信

  1. 建立连接:客户端向服务器发送一个特殊的 HTTP 请求,声明要升级到 WebSocket 协议,请求头包含:
1
2
3
4
5
6
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket // 核心:声明要升级到 WebSocket 协议
Connection: Upgrade // 配合 Upgrade,标识这是协议升级请求
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // 客户端生成的随机字符串,用于验证服务器
Sec-WebSocket-Version: 13 // WebSocket 协议版本(固定为 13)
  1. 服务器响应握手确认:服务器同一升级后,返回 HTTP 101 状态码(协议切换),并在响应头中验证客户端的请求,验证成功后,HTTP 升级为 WebSocket,后续通信不在使用 HTTP 协议。
1
2
3
4
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 服务器用 Sec-WebSocket-Key 计算的结果,客户端会验证
  1. 双向通信:建立连接后,客户端和服务器可随时向对方发送数据,数据格式为二进制帧或文本帧(通常为 JSON 格式传输结构化数据)
  2. 任何一方可发送关闭帧主动断开连接,另一方确认后完成关闭

使用

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 建立 WebSocket 连接(注意协议:ws 对应 HTTP,wss 对应 HTTPS,类似 http/https)
const ws = new WebSocket("wss://example.com/chat"); // wss 是加密的 WebSocket,更安全

// 2. 连接成功回调
ws.onopen = () => {
console.log("WebSocket 连接已建立");
// 发送消息给服务器(文本类型,通常用 JSON)
ws.send(JSON.stringify({ type: "join", username: "张三" }));
};

// 3. 接收服务器消息
ws.onmessage = (event) => {
const data = JSON.parse(event.data); // 解析服务器发送的 JSON 数据
console.log("收到消息:", data);
// 处理消息(如显示到聊天窗口)
};

// 4. 连接关闭回调
ws.onclose = (event) => {
console.log("连接已关闭,代码:", event.code, "原因:", event.reason);
// 可尝试重连
};

// 5. 连接错误回调
ws.onerror = (error) => {
console.error("连接错误:", error);
};

// 6. 主动关闭连接(如用户退出页面)
// ws.close(1000, '正常退出'); // 1000 是正常关闭的状态码

服务器(Spring Boot)

  1. 引入 websocket 依赖
1
2
3
4
5
6
7
8
9
10
11
<!-- Spring WebSocket 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<!-- 可选:如果需要 STOMP 协议支持(高级特性) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 创建配置类
1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
// 注册 WebSocket 端点处理器,使 @ServerEndpoint 注解生效
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
  1. 业务实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

// 定义 WebSocket 端点路径(客户端通过 ws://localhost:8080/ws/{username} 连接)
@ServerEndpoint("/ws/{username}")
@Component
public class WebSocketServer {
// 静态变量,记录当前在线连接数
private static int onlineCount = 0;

// 存储每个客户端对应的 WebSocketServer 对象(线程安全)
private static ConcurrentHashMap<String, WebSocketServer> clients = new ConcurrentHashMap<>();

// 与某个客户端的连接会话,通过它向客户端发送数据
private Session session;

// 客户端用户名
private String username;

/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
this.session = session;
this.username = username;
clients.put(username, this); // 加入客户端映射
addOnlineCount(); // 在线数 +1
System.out.println("用户[" + username + "]连接,当前在线人数:" + getOnlineCount());

// 向客户端发送连接成功消息
sendMessage("连接成功!当前在线人数:" + getOnlineCount());
}

/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
clients.remove(username); // 移除客户端映射
subOnlineCount(); // 在线数 -1
System.out.println("用户[" + username + "]断开连接,当前在线人数:" + getOnlineCount());
}

/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到用户[" + username + "]的消息:" + message);

// 示例:广播消息给所有在线用户
for (WebSocketServer client : clients.values()) {
client.sendMessage("用户[" + username + "]:" + message);
}
}

/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
System.err.println("用户[" + username + "]发生错误");
error.printStackTrace();
}

/**
* 向客户端发送消息
*/
public void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message); // 同步发送文本消息
// 异步发送:this.session.getAsyncRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}

// 在线人数统计(线程安全)
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}