提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、WebSocket是什么?
1.1 为什么会有WebSocket这项技术?
在比较早的时期,很多网站做一种实时推送的功能(服务端需要向客户端主动推送数据),所用的技术都是轮询/短轮询。轮询指的是客户端定期的向服务端发起HTTP请求来获取到服务端返回数据给客户端,WebSocket与轮询的区别如下:
1.2 简介
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 把客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
1.3 优缺点
优点:
- 减少开销:服务端能够主动向客户端推送数据,避免了不必要资源开销;
- 更强的实时性:因为协议是全双工的,所以服务器可以随时向客户端推送数据;相对HTTP请求需要等待客户端发起请求服务端才能够响应数据,延迟明显更少;
- 保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
- 更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
缺点:
- 因为是长连接:所以受网络限制比较大,需要处理好重连机制,比如地铁这种网络比较差的环境容易断开,这个时候就需要重连了;
- 各个浏览器支持程度不一样;
二、编码步骤
1.引入依赖
代码如下(示例):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
</dependencies>
2.编写WebScoket配置类
代码如下(示例):
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
3.编写WebScoketServer服务
代码如下(示例):
@Component
@Slf4j
@ServerEndpoint("/webSocketServer/{myUserId}")
public class WebSocketServer {
/**
* 与客户端的连接会话,需要通过他来给客户端发消息
*/
private Session session;
/**
* 当前用户ID
*/
private String userId;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
* 虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
*/
private static CopyOnWriteArraySet<WebSocketServer> webSockets =new CopyOnWriteArraySet<>();
/**
*用来存在线连接用户信息
*/
private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();
/**
* 连接成功方法
* @param session 连接会话
* @param userId 用户编号
*/
@OnOpen
public void onOpen(Session session , @PathParam("myUserId") String userId){
try {
this.session = session;
this.userId = userId;
webSockets.add(this);
sessionPool.put(userId, session);
log.info("【websocket消息】 用户:" + userId + " 加入连接...");
} catch (Exception e) {
log.error("---------------WebSocket连接异常---------------");
}
}
/**
* 关闭连接
*/
@OnClose
public void onClose(){
try {
webSockets.remove(this);
sessionPool.remove(this.userId);
log.info("【websocket消息】 用户:"+ this.userId + " 断开连接...");
} catch (Exception e) {
log.error("---------------WebSocket断开异常---------------");
}
}
@OnMessage
public void onMessage(@PathParam("myUserId") String userId, String body){
try {
//将Body解析
JSONObject jsonObject = JSONObject.parseObject(body);
//获取目标用户地址
String targetUserId = jsonObject.getString("targetUserId");
//获取需要发送的消息
String message = jsonObject.getString("message");
jsonObject.put("userId" , userId);
if(userId.equals(targetUserId)){
sendMoreMessage(new String[]{targetUserId} , JSONObject.toJSONString(jsonObject));
}else{
sendMoreMessage(new String[]{userId , targetUserId} , JSONObject.toJSONString(jsonObject));
}
} catch (Exception e) {
log.error("---------------WebSocket消息异常---------------");
}
}
/**
* 此为广播消息
* @param message
*/
public void sendAllMessage(String message) {
log.info("【websocket消息】广播消息:"+message);
for(WebSocketServer webSocket : webSockets) {
try {
if(webSocket.session.isOpen()) {
webSocket.session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
log.error("---------------WebSocket消息广播异常---------------");
}
}
}
/**
* 单点消息
* @param userId
* @param message
*/
public void sendOneMessage(String userId, String message) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
log.error("---------------WebSocket单点消息发送异常---------------");
}
}
}
/**
* 发送多人单点消息
* @param userIds
* @param message
*/
public void sendMoreMessage(String[] userIds, String message) {
for(String userId:userIds) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
log.error("---------------WebSocket多人单点消息发送异常---------------");
}
}
}
}
}
项目结构
4.编写一个简单的html进行交互
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<style>
#content {
overflow: auto;
width: 500px;
height: 300px;
background-color: white;
}
body {
background-color: lightblue;
}
</style>
</head>
<body>
<div>
<div id="content">
</div>
<p><label for="myUserId">用户名称:</label><input type="text" name="myUserId" id="myUserId"
placeholder="用户名称"><input type="submit" value="登录"
id="login"/></p>
<p><label for="targetUserId">目标用户名称:</label><input type="text" name="targetUserId" id="targetUserId"
placeholder="目标用户名称"></p>
<textarea name="message" id="message" cols="38" rows="10" placeholder="消息..."></textarea>
<p><input type="submit" id="send" value="发送"/></p>
</form>
</div>
</body>
</html>
<script>
$(function () {
var websocket = null;
$("#login").on('click', function () {
let myUserId = $("#myUserId").val();
websocket = new WebSocket("ws://localhost:8080/webSocketServer/" + myUserId);
// 连接成功后的回调函数
socket();
});
function socket(){
websocket.onopen = function (params) {
console.log('客户端连接成功')
};
websocket.onmessage = function (e) {
var data = JSON.parse(e.data);
if(data.userId == $("#myUserId").val()){
$("#content").append(`<div style="width: 500px;height:40px;line-height: 30px;"><p style="float:right;margin:0;padding:0;">我:${data.message}</p></div>`);
}else{
$("#content").append(`<div style="width: 500px;height:40px;line-height: 30px;"><p style="float:left;margin:0;padding:0;">${data.userId}:${data.message}</p></div>`);
}
};
websocket.onclose = function (evt) {
console.log("关闭客户端连接");
};
websocket.onerror = function (evt) {
console.log("连接失败了");
};
}
$("#send").on('click', function () {
websocket.send(`{"targetUserId": "${$("#targetUserId").val()}" , "message": "${$("#message").val()}"}`);
});
});
</script>
三、测试
1.准备网页
在网页中,输入用户名称点击登录进行登录(在F12中看到客户端连接成功就表示成功登陆了):
2.发送消息
在网页中输入目标用户名称,然后输入需要发送的消息,点击发送即可:
到这简易的私聊聊天室就完成啦!!!