WebSocket简介
WebSocket是一种在单个TCP连接数进行全双工通信的协议,使用WebSocket可以使得用户端和服务器之间的数据交换变得更加简单,它允许服务器主动向客户端推送数据。在WebSocket协议中,浏览器和服务器只需要完成一次握手,两者之间就可以直接创建持久性的连接,并进行数据双向传输。
WebSocket的特点:
- WebSocket使用时需要先创建连接,这使得WebSocket成为一种有状态的协议,在之后的通信过程中可以生了一部分状态信息(例如身份认证等)。
- WebSocket连接在端口80或者443上创建,与HTTP使用的端口相同,这样,基本上所有的防火墙都不会阻止WebSocket连接。
- WebSocket使用HTTP协议进行握手,因此他可以自然而然地集成到网络浏览器和HTTP服务器中,而不需要额外的成本。
- 心跳消息将被反复的发送,进而保持WebSocket连接一直处于活跃状态。
- 使用该协议,当消息启动或者到达的时候,服务器和客户端都可以知道。
- WebSocket连接关闭时将发送一个特殊的关闭信息。
- WebSocket支持跨域,可以避免Ajax的限制。
- HTTP规范要求浏览器将并发连接数限制为每个主机名2个链接,但是当我们使用WebSocket的时候,当握手完成之后,这个限制就不存在了。
WebSocket应用场景:
- 在线股票网站
- 即时聊天
- 多人在线游戏
- 应用集群通信
- 系统性能实时监控
Spring Boot 整合WebSocket
消息群发
文件结构
1、创建项目
首先创建一个SpringBoot 项目,添加如下依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.learn</groupId>
<artifactId>websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>websocket</name>
<description>websocket project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- websocket依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring-boot-starter-websocket依赖是WebSocket相关依赖,其他的都是前端库,使用jar包的形式对这些前端库进行统一管理,使用webjar添加到项目中的前端库,在SpringBoot项目中以及默认添加了静态资源过滤,因此可以直接使用。
2、配置WebSocket
Spring 框架提供了基于WebSocket的STOMP支持,STOMP是一个简单的可交互操作的协议,通常被用于通过中间服务器在客户端之间进行异步消息传递。配置如下:
package com.learn.websocket.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//设置消息代理的前缀,如果消息的前缀是“/topic”,就会将消息转发给消息代理,再有消息代理将消息广播给当前连接的客户端。
registry.enableSimpleBroker("/topic");
//配置一个或多个前缀,通过这些前缀过滤出需要被注解方法处理的消息。
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//定义一个前缀为“/chat”的endPoint,并开启sockjs支持,sockjs可以解决浏览器对WebSocket的兼容性问题,客户端将通过这里配置的URL来建立WebSocket连接。
registry.addEndpoint("/chat").withSockJS();
}
}
3、定义Controller和实体类Message
定义一个Controller用来实现对消息的处理,代码如下:
package com.learn.websocket.controller;
import com.learn.websocket.entity.Message;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Message greeting(Message message) throws Exception{
return message;
}
}
根据第二步的配置,@MessageMapping("/hello")注解的方法将用来接收“/app/hello”路径发送来的消息,在注解方法中华队消息进行处理后,再将消息转发到@SendTo定义的路径上,而@SendTo路径是一个前缀为“/topic”的路径,因此该消息将被交给消息队列broker,再由broker进行广播。
创建一个实体类Message,代码如下:
package com.learn.websocket.entity;
public class Message {
private String name;
private String content;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
4、构建聊天页面
在resources/static目录下创建chat.html页面作为聊天页面,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>群聊</title>
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="webjars/stomp-websocket/stomp.min.js"></script>
<script src="/app.js"></script>
</head>
<body>
<div>
<label for="name">请输入用户名:</label>
<input type="text" id="name" placeholder="用户名">
</div>
<div>
<button id="connect" type="button">连接</button>
<button id="disconnect" type="button" disabled="disabled">断开连接</button>
</div>
<div id="chat" style="display: none;">
<div>
<label for="content">请输入聊天内容:</label>
<input type="text" id="content" placeholder="聊天内容">
</div>
<button id="send" type="button">发送</button>
<div id="greetings">
<div id="conversation" style="display: none;">群聊进行中。。。</div>
</div>
</div>
</body>
</html>
第6~8行是引入外库的JS库,这些JS库在pom.xml文件中通过依赖加入进来。app.js是一个自定义JS,代码如下:
var stompClient = null
function setConnected(connected) {
$("#connect").prop("disabled",connected);
$("#disconnect").prop("disabled",!connected);
if(connected){
$("#conversation").show();
$("#chat").show();
}
else {
$("#conversation").hide();
$("#chat").hide();
}
$("#greetings").html("");
}
function connect() {
if(!$("#name").val()){
return;
}
var socket = new SockJS('/chat');
stompClient = Stomp.over(socket);
stompClient.connect({},function (frame) {
setConnected(true);
stompClient.subscribe('/topic/greetings',function (greeting) {
showGreeting(JSON.parse(greeting.body));
})
})
}
function disconnect() {
if (stompClient !== null){
stompClient.disconnect();
}
setConnected(false);
}
function sendName() {
stompClient.send("/app/hello",{},JSON.stringify({'name': $("#name").val(),'content': $("#content").val()}));
}
function showGreeting(message) {
$("#greetings").append("<div>"+ message.name +":"+message.content+"</div>")
}
$(function () {
$("#connect").click(function () {
connect();
})
$("#disconnect").click(function () {
disconnect();
})
$("#send").click(function () {
sendName();
})
})
代码解释:
- connect方法表示建立一个WebSocket连接,在建立WebSocket连接时,用户必须先输入用户名,然后才能建立连接。
- 第19~26行首先使用SockJS建立连接,然后创建一个STOMP实例发起连接请求,再连接成功的回调方法中,首先调用setConnected(true);方法进行页面的设置,然后调用STOMP中的subscribe方法订阅服务端发送回来的消息,并将服务端发送来的消息展示出来。
- 调用STOMP中的disconnected方法可以断开一个连接。
5、测试
启动Spring Boot项目进行测试,使用两个浏览器测试。