参考链接:
springboot 整合websocket 站内消息 (支持广播式和只给一人发送)单独信息发送 信息群发 统计连接数_苏雨丶的博客-CSDN博客_springboot站内信
WebSocket 注解 (一)_b7410852963的博客-CSDN博客_websocket注解
项目目录结构:
1.创建一个jar类型的项目(使用内置容器),在pom文件中添加websocket的依赖:
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wyh</groupId>
<artifactId>springboot_websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_websocket</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 创建一个websocket的配置类:
package com.wyh.springboot_websocket.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author: wyh
* @description:webSocket配置类
* @date: 12/22/2018 14:51
*/
@Configuration
public class WebSocketConfig {
/*首先在该类中注入一个ServerEndpointExporter的bean,
*ServerEndpointExporter这个bean会自动注册使用了@ServerEndpoint这个注解的websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
3.使用@ServerEndpoint注解声明websocket服务器端点MySocketServer,在该类中处理websocket的具体逻辑。
package com.wyh.springboot_websocket.demo;
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.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @author: wyh
* @description:websocket服务端点,处理具体逻辑
* @date: 12/22/2018 15:09
*/
@ServerEndpoint(value="/myWebsocket/{userid}")//该注解是一个类级别的注解,它的作用就是将当前类定义为一个websocket的服务器端,value的值表示访问路径。
@Component//将该类注册到spring容器中,个人理解这里并没有用到分层,所以不必使用@controller等分化后的注解
public class MySocketServer {
private Session session;//记录当前连接,每个客户端都有对应的session,服务器端通过它向客户端发送消息
//private static Map<String,Session> sessionPool = new HashMap<String,Session>();//存储所有userid及对应的session
//private static Map<String,String> sessionIds = new HashMap<String,String>();//存储所有的sessionid和userid
//因为HashMap是线程不安全的,所以我们使用concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet<MySocketServer> webSocketSet = new CopyOnWriteArraySet<MySocketServer>();//泛型就是当前类
private static int onLineNum = 0;//静态变量,记录当前在线连接数,所有对在线数的操作都应加上synchhronized关键字以确保线程安全
/**
* @author wyh
* @description 有新的链接建立成功时调用该方法
* @param session
* @param userid
* @return void
**/
@OnOpen//该注解表示当有新用户连接时调用该方法
public void open(Session session, @PathParam(value="userid")String userid){//@PathParam(value="userid")表示从访问路径上获取参数赋值给当前形参,value的值与路径上的名字一致
this.session = session;//获取当前session
webSocketSet.add(this);//将当前session加入到set中
addOnlineNum();//在线数增加
}
/**
* @author wyh
* @description 接收到消息时调用该方法
* @param message
* @return void
**/
@OnMessage//该注解表示当服务器端收到客户端发送的消息时调用该方法
public void onMessage(String message){
System.out.println("当前发送人sessionid为"+session.getId()+",发送内容为:"+message);
}
/**
* @author wyh
* @description 断开连接时调用该方法
* @param
* @return void
**/
@OnClose//该注解表示当前用户连接断开时调用该方法
public void onClose(){
webSocketSet.remove(this);
subOnlineNum();//在线数减1
}
/**
* @author wyh
* @description 出现错误时调用
* @param session
* @param error
* @return void
**/
@OnError
public void onError(Session session,Throwable error){
System.out.println("发生错误:"+error.getMessage()+",sessionid为"+session.getId());
error.printStackTrace();
}
/**
* @author wyh
* @description 服务器端主动发送消息
* @param message
* @return void
**/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
System.out.println("发总给指定客户端,sessionid为:"+this.session.getId()+"消息为:"+message);
}
/**
* @author wyh
* @description 群发消息
* @param message
* @return void
**/
public void sendMessages(String message){
for(MySocketServer s : webSocketSet){
try{
s.session.getBasicRemote().sendText(message);
System.out.println("群发给sessionid为:"+s.session.getId()+"的客户端,消息为:"+message);
}catch (IOException e){
e.printStackTrace();
}
}
}
/**
* @author wyh
* @description 获取当前在线数,使用关键字
* @param
* @return int
**/
public synchronized int getOnlineNum(){
return onLineNum;
}
/**
* @author wyh
* @description 在线数加1,注意关键字
* @param
* @return void
**/
public static synchronized void addOnlineNum(){
onLineNum++;
}
/**
* @author wyh
* @description 在线数减1
* @param
* @return void
**/
public static synchronized void subOnlineNum(){
onLineNum--;
}
}
4.Controller:
package com.wyh.springboot_websocket.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author: wyh
* @description: 推送消息的controller
* @date: 12/22/2018 17:26
*/
@Controller
public class WebSocketController {
@Autowired
private MySocketServer socketServer;
//访问服务器端的统计信息页面
@RequestMapping("/count")
public String onLineNum(Model model){
int count = socketServer.getOnlineNum();
model.addAttribute("num",count);//把在线数放到model中,前端直接获取
return "server";//服务端发送信息的页面
}
//访问客户端首页
@RequestMapping("/index")
public String index(){
return "client";//跳转到客户端首页
}
@RequestMapping("/sendMessages")
public String sendMessages(String msg){
socketServer.sendMessages(msg);
return "server";//如果该方法返回void,那么运行时会抛出org.thymeleaf.exceptions.TemplateInputException: Error resolving template
}
}
5.application.properties:
#thymeleaf start
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
#thymeleaf end
6.客户端页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>客户端首页</title>
<!--这里必须是双标签-->
<!--<script th:src="@{/js/jquery.min.js}"/>-->
<script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
请输入您的用户id<input type="text" id="user"/>
<input type="button" value="连接" onclick="connect()"/><br/>
请填写要发送的内容<input type="text" id="writeMsg"/>
<input type="button" value="发送" onclick="sendMsg()"/>
<script type="text/javascript">
//放在这里是获取不到值的,应该放在function里面
//var user = $("#user").val();
var ws = null;//不能在send()里再新建ws,因为ws建立连接时已存在
//创建连接
function connect(){
var user = $("#user").val();
alert(user);
if(user != null){
if('WebSocket' in window){
ws = new WebSocket("ws://localhost:8080/myWebsocket/"+user);//这里的Username其实我们还是用userid
}else if('MozWebSocket' in window){
ws = new MozWebSocket("ws://localhost:8080/myWebsocket/"+user);
}else{
alert("该浏览器不支持websocket");
}
ws.onmessage = function(evt){
alert(evt.data);
}
ws.onclose = function(evt){
alert("连接中断");
}
ws.onopen = function(evt){
alert("连接成功");
}
}else {//userid为空
alert("请输入您的uerid");
}
}
//发送消息
function sendMsg(){
ws.send($("#writeMsg").val());
}
</script>
</body>
</html>
7.服务器端管理页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>服务器端发送信息的页面</title>
<script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
当前在线人数总计<div id="sum" th:text="${num}"></div>
向客户端发送消息<input type="text" id="msg"/><br/>
<input type="button" value="全体发送" onclick="sendAll()"/>
<script type="text/javascript">
function sendAll() {
var msg = $("#msg").val();
if(msg != null){
$.ajax({
method:'get',
url:'/sendMessages',
data:{
msg:msg
},
success:function (data) {
console.log(data);
}
}
)
}else{
alert("请填写要发送的内容");
}
}
</script>
</body>
</html>
8.启动服务,访问客户端,打开多个会话(此处创建3个会话),再访问服务器发送消息页面(此时还未创建会话),截图中的第三个id(765)可以忽略,忘了刷新:
使三个会话都创建连接:
以上可以看到三个会话连接都已创建成功,然后我们分别向服务器发送消息:
此时访问服务器端发送消息的页面:
在服务器端的页面向所有当前会话发送消息:
点击全体发送后,我们再看当前三个客户端,都收到了服务器端发来的信息:
最后来看一下控制台的打印信息: