SpringBoot从入门到精通系列三:WebSocket
使用WebSocket能让客户端与服务端进行双向的实时通信。当客户端与服务端之间交互的内容较多或对实时性要求较高时,可使用WebSocket。
使用@ServerEndpoint开发WebSocket
Springboot的spring-boot-starter-websocket.jar为WebSocket提供了丰富的支持,使用这种方式来开发WebSocket服务器端程序非常简单,只要如下两步即可。
定义一个WebSocket处理类,该处理类有两种开发方式:
- 直接使用JDK提供的WebSocket注解修饰处理方法,并使用@ServerEndpoint注解修饰该处理类即可。
- 实现WebSocketHandler接口,并实现该接口中定义的各种处理方法。
采用第一种方式开发WebSocket处理类,这一步只需要在Spring容器中配置一个ServerEndpointExporter Bean即可。采用第二种方式开发WebSocket处理类,这一步就需要使用WebSocketConfigurer来配置WebSocket。
1.定义pom.xml
pom.xml
继承spring-boot-starter-parent,并添加spring-boot-starter-websocket.jar依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定继承spring-boot-starter-parent POM文件 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/>
</parent>
<groupId>org.crazyit</groupId>
<artifactId>ServerEndpoint</artifactId>
<version>1.0-SNAPSHOT</version>
<name>ServerEndpoint</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 定义Spring Boot Maven插件,可用于运行Spring Boot应用 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.定义WebSocket处理类
接下来定义WebSocket处理类,并在该类中定义连接成功、收到消息、出现错误、连接关闭时的处理方法。
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.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@ServerEndpoint("/websocket/{name}")
public class WebSocketEndpoint
{
public static Map<Session, String> socketMap = new ConcurrentHashMap<>();
// 连接建立成功触发的方法
@OnOpen
public void onOpen(@PathParam("name") String name, Session session)
{
socketMap.put(session, name);
}
// 连接关闭时触发的方法
@OnClose
public void onClose(Session session)
{
socketMap.remove(session);
}
// 收到客户端消息后触发的方法
@OnMessage
public void onMessage(String message, Session session)
{
System.out.printf("收到来自%s的消息:%s%n", session, message);
try
{
var name = socketMap.get(session);
for (var client: socketMap.keySet()) // ①
{
client.getBasicRemote().sendText(name + "说:" + message);
}
} catch (IOException e)
{
e.printStackTrace();
}
}
// 发生错误时触发的方法
@OnError
public void onError(Session session, Throwable error)
{
System.out.println("发生错误");
socketMap.remove(session);
}
}
- 该处理类定义了onOpen()、onClose()、onMessage()、onError()四个方法,并分别使用了@OnOpen、@OnClose、@OnMessage、@OnError注解修饰,表明这四个方法分别在建立连接、关闭连接、收到消息、遇到错误时被触发。
- 使用这种方式开发WebSocket处理类更灵活,比如粗体字代码定义的onOpen()方法,该方法额外定义了一个用@PathParam修饰的形参,通过该形参即可获取路径参数。
- 上面处理类使用Map保存WebSocketSession与客户端之间的对应关系,每当服务器接收到客户端发送过来的消息时,程序都会通过循环遍历Map中的每一个WebSocketSession,并逐一向它们发送接收到的消息,这样任意客户端发送消息都会被广播给所有客户端。
3.配置ServerEndpointExporter Bean
如下配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig
{
@Bean
public ServerEndpointExporter serverEndpointExporter()
{
return new ServerEndpointExporter();
}
}
- 上面配置类在Spring容器中配置了一个ServerEndpointExporter Bean,它专门负责读取Bean类上的@ServerEndpoint注解,并将该Bean导出为WebSocket Endpoint
3.使用JavaScript来开发WebSocket客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 基于WebSocket的多人聊天 </title>
<script type="text/javascript">
// 定义Web Socket对象
var webSocket = null;
let sendMsg = function()
{
if (webSocket == null || webSocket.readyState != 1)
{
document.getElementById('show').innerHTML
+= "还未连接服务器,请先连接WebSocket服务器<br>";
return;
}
let inputElement = document.getElementById('msg');
// 发送消息
webSocket.send(inputElement.value);
// 清空单行文本框
inputElement.value = "";
}
let connect = function()
{
let name = document.getElementById('name').value.trim();
if (name == null || name == "")
{
document.getElementById('show').innerHTML
+= "用户名不能为空<br>";
return;
}
if (webSocket && webSocket.readyState == 1)
{
webSocket.close();
}
webSocket = new WebSocket("ws://127.0.0.1:8080/websocket/" + name);
webSocket.onopen = function()
{
document.getElementById('show').innerHTML
+= "恭喜您,连接服务器成功!<br>";
document.getElementById('name').value = "";
// 为onmessage事件绑定监听器,接收消息
webSocket.onmessage= function(event)
{
// 接收、并显示消息
document.getElementById('show').innerHTML
+= event.data + "<br>";
}
};
}
</script>
</head>
<body>
<input type="text" size="20" id="name" name="name"/>
<input type="button" value="连接" onclick="connect();"/>
<div style="width:600px;height:240px;
overflow-y:auto;border:1px solid #333;" id="show"></div>
<input type="text" size="80" id="msg" name="msg"/>
<input type="button" value="发送" onclick="sendMsg();"/>
</body>
</html>
上面的JavaScript代码用到了HTML5的WebSocket规范,该页面代码需要支持HTML5规范的浏览器中运行。
4.主程序类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App
{
public static void main(String[] args)
{
SpringApplication.run(App.class, args);
}
}