目录

前言

一、分析

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 依赖。

org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-websocket

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;@Componentpublic 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 //启动 WebSocketpublic 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 请求,代码如下:

                Document                    // 创建 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);        }    

2.3、执行效果

启动程序,输入对应URL,如下结果;

发送消息,点击按钮(相当于主机A 给主机B 发送消息,这里实现的是一个简易版,是一个回显服务),如下结果:

最后关闭浏览器,断开连接:

这样一个简单 demo 就完成了~