目录
前言
一、分析
1.1、qq聊天功能分析
1.2、WebSocket介绍
1.2.1、什么是消息推送呢?
1.2.2、原理解析
1.2.3、报文格式
二、简易demo
2.1、后端实现
2.1.1、引入依赖
2.1.2、继承TextWebSocketHandler
2.1.3、实现 WebSocketConfigurer 接口
2.2、前端实现
2.3、执行效果
前言
基于 WebSocket、Spring Boot 下,用一个简易的小 demo 讲解一下“QQ聊天”功能原理~
一、分析
1.1、qq聊天功能分析
用户看到的实际效果:用户A 给用户B 发送了一条消息,用户B在聊天框看到了消息,接着用户B回复消息......
底层实现:主机A 给服务器发送了一个 HTTP 请求,这个请求中就包含了用户A 要给用户B 发送的消息,服务器将收到的请求进行解析,在将响应发送给 主机B,这个响应中就包含了用户A 要发送的消息~
对于以上场景,我们应该如何实现呢?接着往下看!
1.2、WebSocket介绍
WebSocket 是从 HTML5 开始支持的一种网页端和服务端保持长连接的 消息推送机制。
1.2.1、什么是消息推送呢?
传统的 web 程序都是 “一问一答” 的形式,客户端给服务器发送了个 HTTP 请求,服务器根据请求计算响应返回客户端。显然,这种传统的 web 程序如果客户端不主动发起请求,服务器就无法主动给客户端发送响应~
Ps:若想使用原生的 HTTP 协议实现消息推送,需要进行“轮询”,反复询问服务器是否收到请求,成本太高
而 WebSocket 则是更接近 TCP 这种级别的通信方式——一旦连接建立完成,客户端或服务器都可以主动向对方发送数据。
1.2.2、原理解析
WebSocket 协议本质上是基于 TCP 协议的,为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,通过这个附加头信息完成握手过程~
1.2.3、报文格式
- FIN: 为 1 表示要断开 websocket 连接。
- RSV1/RSV2/RSV3: 保留位, 一般为 0。
- opcode: 操作代码. 决定了如何理解后面的数据载荷。
- mask: 表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。(掩码算法主要是为了安全考虑的,避免缓冲区溢出攻击)。
- Payload length:数据载荷的长度,单位是字节。为7位,或7+16位,或1+64位。
- payload data: 报文携带的载荷数据。
Spring 中内置了 websocket ,可以直接进行使用,如何使用?往下看~
二、简易demo
2.1、后端实现
2.1.1、引入依赖
创建一个 Spring Boot 项目,接着我们需要引入 Spring Web 依赖,以及 Web Socket 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.1.2、继承TextWebSocketHandler
这里我们先创建一个类,自定义名为 TestAPI ,继承 TextWebSocketHandler 类,重写这个类下4个最关键的方法:
- afterConnectionEstablishe:客户端与服务器成功建立连接以后会调用此方法。
- handleTextMessage:客户端通过 WebSocket 类下的 send 方法发送给服务器数据时会调用此方法。
- handleTransportError:客户端与服务器连接异常时会调用此方法。
- afterConnectionClosed:客户端与服务器断开连接时会调用此方法。
这个类就是用来处理 WebSocket 相关请求的~
于是,我们就可这样写:
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@Component
public class TestAPI extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("连接成功");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到消息:" + message.getPayload());
//收到消息后向客户端反馈信息(这里反馈相同数据)
session.sendMessage(message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("连接异常");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("连接关闭");
}
}
2.1.3、实现 WebSocketConfigurer 接口
第一步,创建一个类,自定义类名为WebSocketConfig,实现 WebSocketConfigurer接口,这个接口下只需要实现一个方法 registerWebSocketHandlers,这个类就是用来注册添加 WebSocket (当前项目要添加的就是我们刚刚写的 TestAPI 这个类)以及注册路由(这个路由就将客户端与服务器联系起来了:前端创建 WebSocket 实例时,就需要写下对应的路由)。
第二步、注入 TestAPI 类,最后使用 addHander方法添加注册这个类,基于这个类才能进一步的找到 TestAPI 这个类,去处理 WebSocket相关的请求~
代码实现如下:
import com.example.demo.api.TestAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket //启动 WebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private TestAPI testAPI;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(testAPI, "/test");
}
}
Ps:
@EnableWebSocket
注解是用来让 Spring 框架认识这个类是用来配置 webSocket 的类,基于这个类才能进一步的找到 TestAPI 这个类,去处理 WebSocket相关的请求~
2.2、前端实现
前端需要创建 WebSocket 实例,并写下相应路由,这个路由就是与后端 webSocket 相对应的那个路由,如下:
接着前端也可以通过 WebSocket 的4个重要方法,根据响应打印对应日志:
- onopen:客户端与服务器建立连接后会调用该方法。
- onmessage:客户端收到服务器响应后会触发该方法。
- onerror:客户端与服务器连接异常时会触发该请求。
- conclose:客户端与服务器断开连接时会触发该请求。
最后通过按钮输入框的形式来发送相应的 HTTP 请求,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" id="message">
<button id="submit">提交</button>
<script>
// 创建 websocket 示例
let websocket = new WebSocket("ws://127.0.0.1:8080/test");
websocket.onopen = function() {
console.log("建立连接");
}
websocket.onmessage = function(e) {
console.log("收到消息: " + e.data);
}
websocket.onerror = function() {
console.log("连接异常");
}
websocket.onclose = function() {
console.log("连接关闭");
}
let input = document.querySelector("#message");
//点击按钮发送请求
let button = document.querySelector("#submit");
button.onclick = function() {
console.log("发送消息:" + input.value);
websocket.send(input.value);
}
</script>
</body>
</html>
2.3、执行效果
启动程序,输入对应URL,如下结果;
发送消息,点击按钮(相当于主机A 给主机B 发送消息,这里实现的是一个简易版,是一个回显服务),如下结果:
最后关闭浏览器,断开连接:
这样一个简单 demo 就完成了~