SpringBoot整合WebSocket实现聊天室流程全解
作者:欲无缘 发布时间:2021-07-03 23:35:21
标签:SpringBoot,WebSocket,聊天室
什么是WebSocket
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket通信模型
为什么需要WebSocket
http 通信是单向的,发送请求获取响应,没有请求也就没有响应。
简单理解:
HTTP 打电话:客户端问一句服务端答一句
WebSocket 打电话:双向对话
Websocket与http的关系
相同点:
都是基于tcp的,都是可靠性传输协议
都是应用层协议
不同点:
WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息
HTTP是单向的
WebSocket是需要浏览器和服务器握手进行建立连接的而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接。
SpringBoot集成WebSocket
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocketConfig
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocketService
@Slf4j
@Service
@ServerEndpoint(value = "/myService/{userId}")
public class WebSocketService {
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static AtomicInteger onlineCount = new AtomicInteger(0);;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static ConcurrentHashMap<String,WebSocketService> webSocketMap = new ConcurrentHashMap<>();
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的session对象。
*/
private static ConcurrentHashMap<String,Session> sessionMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
private String userId = "";
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session,@PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
if(webSocketMap.containsKey(userId) && sessionMap.containsKey(userId)){
webSocketMap.remove(userId);
sessionMap.remove(userId);
sessionMap.put(userId,session);
webSocketMap.put(userId,this);
}else{
webSocketMap.put(userId,this);
sessionMap.put(userId,session);
addOnlineCount();
}
log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
subOnlineCount();
}
log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String message, Session session) {
this.session = session;
log.info("收到客户端消息 -> {}",message);
//服务端收到客户端的消息并推送给客户端
sendMessage(message);
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
log.error(error.getMessage());
}
/**
* 实现服务器主动推送 可以通过controller调用此方法实现主动推送
*/
public void sendMessage(String message){
try {
Set<Map.Entry<String, Session>> entries = sessionMap.entrySet();
for (Map.Entry<String, Session> next : entries) {
Session session = next.getValue();
session.getBasicRemote().sendText(this.userId + "说" + message);
}
} catch (IOException e) {
log.error(e.getMessage());
}
}
public static synchronized int getOnlineCount() {
return onlineCount.get();
}
public static synchronized void addOnlineCount() {
WebSocketService.onlineCount.getAndIncrement();
}
public static synchronized void subOnlineCount() {
WebSocketService.onlineCount.getAndDecrement();
}
}
WebSocket.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>websocket通讯</title>
</head>
<body>
<p>userId:<input id="userId" name="userId" type="text" value="10"></p>
<p>msg:<input id="contentText" name="contentText" type="text" value="hello websocket"></p>
<p>操作:<button onclick="openSocket()">开启socket</button></p>
<p>操作:<button onclick="sendMessage()">发送消息</button></p>
</body>
<script type="application/javascript">
let socket;
function openSocket() {
if(socket != null){
socket.close();
socket = null;
}
let userId = document.getElementById('userId').value
socket = new WebSocket("ws://localhost:9000/myService/"+userId);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
};
//获得消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
};
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
}
}
function sendMessage() {
let contentText = document.getElementById('contentText').value
socket.send(contentText);
}
</script>
</html>
测试
来源:https://blog.csdn.net/qq_29917503/article/details/128343049


猜你喜欢
- import java.io.ByteArrayInputStream; import java.io.FileOutputSt
- 背景最近项目中需要上传视频文件,由于视频文件可能会比较大,但是我们应用服务器tomcat设置单次只支持的100M,因此决定开发一个分片上传接
- 本文实例讲述了C#提取网页中超链接link和text部分的方法。分享给大家供大家参考,具体如下:string s = "..&qu
- 总之是用jdbc 的游标移动package com.sp.person.sql.util; import java.sql.Connecti
- 前言目前,企业项目的开发过程中,往往会使用配置文件来做一些配置项来实现项目部署的灵活性,避免硬编码的方式在环境变化时需要对代码进行重新编译。
- 对接前端后效果展示如图:1、CPU相关信息实体类/** * CPU相关信息 * * @author csp */public class
- 关于C# Timer类 在C#里关于定时器类就有3个C# Timer使用的方法1.定义在System.Windows.Forms
- 本文研究的主要是Java编程中的复用类,那么到底复用类是什么东西,又有什么用法,下面具体介绍。看了老罗罗升阳的专访,情不自禁地佩服,很年轻,
- 一、WebRequestMethods.Ftp类:表示可与 FTP 请求一起使用的 FTP 协议方法的类型。AppendFile:表示要用于
- 你以前听到的谈论关于Java8的所有都是围绕lambda表达式. 但它仅仅是Java8的一部分. Java 8 有许多新特性—一些强大的新类
- Java 8 , Lambda + foreach 语法糖, 写起来非常的 cleanpublic static void main(Str
- 前言这几天专门研究了下JNI编程,在网上找了好多资料,不过好多都是以前的,没有更新,而且有的还是错误的,让人不得不吐槽一把。所以觉得自己来一
- 假如使用绝对路径,没有任何问题,就是移植性不太好。假如使用相对路径,则要注意当前路径“.”是在哪儿?一般我们都会在配置文件中加入log文件的
- 1,什么是字符编码? 字符(Character)是文字与符号的总称,包括文字、图形符号、数学符号等。一组
- 一般情况下是不可以用static修饰类的。如果一定要用static修饰类的话,通常static修饰的是匿名内部类。在一个类中创建另外一个类,
- 这篇文章主要介绍了如何使用HttpClient发送java对象到服务器,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学
- 本文实例讲述了C#实现让ListBox适应最大Item宽度的方法。分享给大家供大家参考。具体实现方法如下:private void butt
- Object是所有类的父类,任何类都默认继承Object。一、Object类中的方法1.clone方法保护方法,实现对象的浅复制,只有实现了
- Jackson,我感觉是在Java与Json之间相互转换的最快速的框架,当然Google的Gson也很不错,但是参照网上有人的性能测试,看起
- 一个专门实现访问sql server数据库增删改查的操作代码,分享给大家,具体内容如下using System;using System.C