SpringBoot实现WebSocket即时通讯的示例代码
作者:码奴生来只知道前进~ 发布时间:2022-06-14 19:59:36
标签:SpringBoot,WebSocket,即时通讯
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.3</version>
</dependency>
2、WebSocketConfig 开启WebSocket
package com.shucha.deveiface.web.config;
/**
* @author tqf
* @Description
* @Version 1.0
* @since 2022-04-12 15:35
*/
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启WebSocket
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
3、WebSocketServer
package com.shucha.deveiface.web.ws;
/**
* @author tqf
* @Description
* @Version 1.0
* @since 2022-04-12 15:33
*/
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint("/webSocket/{userId}")
@Slf4j
public class WebSocketServer {
private Session session;
private String userId;
/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
private static int onlineCount = 0;
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
/**
* concurrent包的线程安全set,用来存放每个客户端对应的MyWebSocket对象
*/
private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap();
/**
* 为了保存在线用户信息,在方法中新建一个list存储一下【实际项目依据复杂度,可以存储到数据库或者缓存】
*/
private final static List<Session> SESSIONS = Collections.synchronizedList(new ArrayList<>());
/**
* 建立连接
* @param session
* @param userId
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
webSocketSet.add(this);
SESSIONS.add(session);
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId,this);
} else {
webSocketMap.put(userId,this);
addOnlineCount();
}
// log.info("【websocket消息】有新的连接, 总数:{}", webSocketSet.size());
log.info("[连接ID:{}] 建立连接, 当前连接数:{}", this.userId, webSocketMap.size());
}
/**
* 断开连接
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
subOnlineCount();
}
// log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());
log.info("[连接ID:{}] 断开连接, 当前连接数:{}", userId, webSocketMap.size());
}
/**
* 发送错误
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.info("[连接ID:{}] 错误原因:{}", this.userId, error.getMessage());
error.printStackTrace();
}
/**
* 收到消息
* @param message
*/
@OnMessage
public void onMessage(String message) {
// log.info("【websocket消息】收到客户端发来的消息:{}", message);
log.info("[连接ID:{}] 收到消息:{}", this.userId, message);
}
/**
* 发送消息
* @param message
* @param userId
*/
public void sendMessage(String message,Long userId) {
WebSocketServer webSocketServer = webSocketMap.get(String.valueOf(userId));
if (webSocketServer!=null){
log.info("【websocket消息】推送消息, message={}", message);
try {
webSocketServer.session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
log.error("[连接ID:{}] 发送消息失败, 消息:{}", this.userId, message, e);
}
}
}
/**
* 群发消息
* @param message
*/
public void sendMassMessage(String message) {
try {
for (Session session : SESSIONS) {
if (session.isOpen()) {
session.getBasicRemote().sendText(message);
log.info("[连接ID:{}] 发送消息:{}",session.getRequestParameterMap().get("userId"),message);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取当前连接数
* @return
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* 当前连接数加一
*/
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
/**
* 当前连接数减一
*/
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
4、测试连接发送和接收消息
package com.shucha.deveiface.web.controller;
import com.alibaba.fastjson.JSONObject;
import com.shucha.deveiface.web.ws.WebSocketServer;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author tqf
* @Description
* @Version 1.0
* @since 2022-04-12 15:44
*/
@RestController
@RequestMapping("/web")
public class TestWebSocket {
@Autowired
private WebSocketServer webSocketServer;
/**
* 消息发送测试
*/
@GetMapping("/test")
public void test(){
for (int i=1;i<4;i++) {
WebsocketResponse response = new WebsocketResponse();
response.setUserId(String.valueOf(i));
response.setUserName("姓名"+ i);
response.setAge(i);
webSocketServer.sendMessage(JSONObject.toJSONString(response), Long.valueOf(String.valueOf(i)));
}
}
/**
* 群发消息测试(给当前连接用户发送)
*/
@GetMapping("/sendMassMessage")
public void sendMassMessage(){
WebsocketResponse response = new WebsocketResponse();
response.setUserName("群发消息模板测试");
webSocketServer.sendMassMessage(JSONObject.toJSONString(response));
}
@Data
@Accessors(chain = true)
public static class WebsocketResponse {
private String userId;
private String userName;
private int age;
}
}
5、在线测试地址
websocket 在线测试
6、测试截图
访问测试发送消息:http://localhost:50041//web/test
测试访问地址:ws://192.168.0.115:50041/webSocket/1 wss://192.168.0.115:50041/webSocket/2
来源:https://blog.csdn.net/tanqingfu1/article/details/124127295


猜你喜欢
- Android页面嵌套了一个h5,H5页面内部有用户登陆页面,发现h5页面的登陆功能无法使用,一直登陆失败。和web那边商量一会,发现js写
- Java调用Linux系统命令有时候,我们在使用Java做一些操作时,可能性能上并不能达到我们满意的效果,就拿最近工作中的遇到的一个场景来说
- app的启动方式: 1.)冷启动 当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启
- 简介Trie树,又称为前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由
- android跑马灯出现重复跳动、不滚动问题,本文给出解决方案,供大家参考。原因:页面有View被重新绘制了、焦点被抢占例如:1、TextV
- 为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。包的作用1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用
- 一、 看效果二、上代码package com.framework.widget;import android.app.Activity;im
- mybatis自动生成代码(实体类、Dao接口等)是很成熟的了,就是使用mybatis-generator插件。 它是一个开源的插件,使用m
- 一、获取当前时间, 格式为: yyyy-mm-dd hh-mm-ss
- 本文实例讲述了C#实现读取指定盘符硬盘序列号的方法。分享给大家供大家参考,具体如下:using System;using System.IO
- 面向对象编程(Object Oriented Programming)有三大特性:封装、继承、多态。在这里,和大家一起加深对三者的理解。封装
- 说明compose中我们的所有ui操作,包括一些行为,例如:点击、手势等都需要使用Modifier来进行操作。因此对Modifier的理解可
- 带着问题 往下看 (namesrv)我们在写组件的时候 怎么管理version如果现在让你 维护一个 各个jar包公用的属性System.e
- @Value取值为NULL的问题在spring mvc架构中,如果希望在程序中直接使用properties中定义的配置值,通常使用一下方式来
- 废话不多说了,直接给大家贴关键代码了。具体代码如下所示:using System;using System.Collections.Gene
- 一.优先队列的应用优先队列在程序开发中屡见不鲜,比如操作系统在进行进程调度时一种可行的算法是使用优先队列,当一个新的进程被fork()出来后
- 大家好,好久不见了,最近由于工作特别繁忙,已经有一个多月的时间没写博客了,我也是深感惭愧。那么今天的这篇既然是阔别了一个多月的文章,当然要带
- 1:普通实现99乘法表太简单,是个程序员都会,实现如下:package test.ms;public class Test99 {publi
- package 移位运算;public class 移位运算 { public static void main(String[] args
- 前言不积跬步无以至千里,不积小流,无以成江海在公司一般来说,都只会接触一些CRUD的业务,很多时候可能你想设计很多的代码结构,但是时间不允许