WebSocket资料参考:

https://www.jianshu.com/p/d79bf8174196 

使用SpringBoot整合参考:

https://blog.csdn.net/KeepStruggling/article/details/105543449

  

一、通信实现

后端部分:

直接使用Springboot,依赖只有内嵌tomcat和对应的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.3.RELEASE</version>
</parent> <groupId>cn.cloud9</groupId>
<artifactId>WebSocket</artifactId>
<version>1.0-SNAPSHOT</version> <properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties> <dependencies>
<!-- 内嵌Tomcat库来提供WebSocketAPI -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- Spring对WebSocket的扩展Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies> </project>

  

定义WebSocket接口

package cn.cloud9.endpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* @author OnCloud9
* @description
* @project WebSocket
* @date 2022年06月15日 20:13
*
* ws://localhost:8080/ws/test
*/
@Component
@ServerEndpoint("/ws/test")
public class TestEndpoint {
private static final Logger LOGGER = LoggerFactory.getLogger(TestEndpoint.class); private static final Map<String, Session> CLIENT_SESSION_MAP = new ConcurrentHashMap<>(); /**
* 侦测客户端向此服务建立连接,此终端实例会新创建出来
* @param session
*/
@OnOpen
public void onOpen(Session session) {
LOGGER.info("客户端 {} 开启了连接", session.getId());
// 默认按照ID保管存放这个客户端的会话信息
CLIENT_SESSION_MAP.put(session.getId(), session);
} /**
* 侦测客户端异常
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
LOGGER.info("客户端 {} 连接异常... 异常信息:{}", session.getId(), error.getMessage());
LOGGER.error(error.getMessage());
} /**
* 侦测客户端向此服务端发送消息
* @param session
* @param message
*/
@OnMessage
public void onMessage(Session session, String message) {
// 可根据会话ID或者message中自定义唯一标识从容器中取出会话对象来进行操作
LOGGER.info("收到消息, 来自客户端 {}, 消息内容 -> {}", session.getId(), message);
} /**
* 侦测客户端关闭事件
* @param session
*/
@OnClose
public void onClose(Session session) {
LOGGER.info("客户端 {} 关闭了连接...", session.getId());
// 客户端关闭时,从保管容器中踢出会话
CLIENT_SESSION_MAP.remove(session.getId());
} /**
* 给所有客户端发送消息
* @param message
*/
public static void sendMessageForAllClient(String message) {
CLIENT_SESSION_MAP.values().forEach(session -> {
try {
LOGGER.info("给客户端 {} 发送消息 消息内容: {}", session.getId(), message);
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
LOGGER.info("给客户端 {} 发送消息失败, 异常信息:{}", session.getId(), e.getMessage());
}
});
} }

配置WebSocket接口暴露器

package cn.cloud9.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter; /**
* @author OnCloud9
* @description
* @project WebSocket
* @date 2022年06月15日 20:42
*/
@Configuration
public class WebSocketConfig {
/**
* 如果使用Springboot默认内置的tomcat容器,则必须注入ServerEndpoint的bean;
* 如果使用外置的web容器,则不需要提供ServerEndpointExporter,下面的注入可以注解掉
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}

  

写一个Controller,通过一般Http接口向WebSocket客户端推送消息

package cn.cloud9.controller;

import cn.cloud9.endpoint.TestEndpoint;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; /**
* @author OnCloud9
* @description
* @project WebSocket
* @date 2022年06月15日 20:46
*/
@RestController
@RequestMapping("/api/ws")
public class WebSocketController { /**
* http://localhost:8080/api/ws/send
* @param message
* @return
*/
@GetMapping("/send")
public boolean send(@RequestParam String message) {
TestEndpoint.sendMessageForAllClient(message);
return true;
}
}

  

前端页面:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket 页面客户端</title>
</head>
<body> <div>
<button onclick="initConnection()">开启WebSocket连接</button>
</div>
<div>
<input type="text" id="message" ><button onclick="sendMessageFromClient()">发送消息</button>
</div> <div>
<button onclick="closeConnection()">关闭连接</button>
</div> <script>
const connectionUrl = 'ws://localhost:8080/ws/test'
var webSocket = null function initConnection() {
webSocket = new WebSocket(connectionUrl) webSocket.onopen = (event) => {
console.log('建立新的WebSocket连接')
} webSocket.onmessage = (event) => {
const message = JSON.stringify(event.data)
console.log(`收到服务端发送的消息,消息内容 -> ${message}`)
} webSocket.onerror = (error) => {
console.log(`WebSocket连接异常 -> ${JSON.stringify(error)}`)
} webSocket.onclose = (event) => {
console.log(`WebSocket连接关闭 -> ${JSON.stringify(event)}`)
}
} function sendMessageFromClient() {
const message = document.querySelector('#message').value
webSocket.send(JSON.stringify({ message: message }))
} function closeConnection() {
webSocket.close()
}
</script>
</body>
</html>

  

二、解决客户端标识区分问题:

WebSocket提供了一个@PathParam注解

在开启和关闭时通过该注解的参数传递URL路径值

通过这个在这个路径值放置唯一标识即可区分客户端连接

菜坑点:

1、路径值不能随意放置,必须是在最后面

2、可以放置JSON,但是接收的参数将会移除JSON对象的大括号符号,因为路径值的占位符原因...,解决办法就是追加大括号即可

完整代码:

添加了身份区分的终端案例:

package cn.cloud9.server.struct.websocket;

import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service; 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; import static cn.cloud9.server.struct.websocket.WsMessageEndPoint.PATH; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月09日 下午 09:56
*/
@Slf4j
@Service
@ServerEndpoint(PATH)
public class WsMessageEndPoint {
/**
* localhost:8080/websocket/message
* 路径参数用于客户端身份标识,区分每个客户端的连接
*/
public static final String PATH = "/websocket/message/{token}"; /**
* 客户端会话管理容器
*/
private static final Map<String, Session> CLIENT_SESSION_MAP = new ConcurrentHashMap<>(); /**
* 侦测客户端开启连接?,通过客户端身份标识 重复创建连接则覆盖原有的连接
* @param session
*/
@OnOpen
public void onConnectionOpen(Session session, @PathParam("token") String clientToken) {
log.info("客户端 {} 开启了连接", clientToken);
final Map<String, String> map = JSONObject.parseObject(clientToken, Map.class);
// 默认按照ID保管存放这个客户端的会话信息
CLIENT_SESSION_MAP.put(map.get("userId"), session);
} /**
* 侦测客户端关闭事件
* @param session
*/
@OnClose
public void onClose(Session session, @PathParam("token") String clientToken) {
final Map<String, String> map = JSONObject.parseObject(clientToken, Map.class);
log.info("客户端 {} 关闭了连接...", clientToken);
// 客户端关闭时,从保管容器中踢出会话
final String userId = map.get("userId");
CLIENT_SESSION_MAP.remove(userId);
} /**
* 侦测客户端异常
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("客户端 {} 连接异常... 异常信息:{}", session.getId(), error.getMessage());
} /**
* 收到客户端消息
* @param session
*/
@OnMessage
public void receiveClientMessage(String message, Session session) throws IOException {
log.info("来自客户端 {} 的消息: {}", session.getId(), message);
session.getBasicRemote().sendText("服务器已收到");
} /**
* 给所有客户端发送消息
* @param message
*/
public static void sendMessageForAllClient(String message) {
CLIENT_SESSION_MAP.values().forEach(session -> {
try {
log.info("给客户端 {} 发送消息 消息内容: {}", session.getId(), message);
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
log.info("给客户端 {} 发送消息失败, 异常信息:{}", session.getId(), e.getMessage());
}
});
} @SneakyThrows
public static void sendMessageForClient(String clientId, String message) {
final Session session = CLIENT_SESSION_MAP.get(clientId);
session.getBasicRemote().sendText(message);
}
}

 

服务端发送消息接口:

给PostMan调用,然后看对应的客户端是否更新消息

package cn.cloud9.server.test.controller;

import cn.cloud9.server.struct.websocket.WsMessageEndPoint;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.Map; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月09日 下午 10:03
*/
@RestController
@RequestMapping("/websocket/sender")
public class WsMessageController { @PostMapping("/all")
public void sendMessageToAllClient(@RequestBody Map<String, String> map) {
final String text = map.get("text");
WsMessageEndPoint.sendMessageForAllClient(text);
} @PostMapping("/one")
public void sendMessageToClient(@RequestBody Map<String, String> map) {
final String text = map.get("text");
final String client = map.get("client");
WsMessageEndPoint.sendMessageForClient(client, text);
} }

  

浏览器客户端:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket 页面客户端</title>
</head>
<body> <div>
<button onclick="initConnection()">开启WebSocket连接</button>
</div>
<div>
<input type="text" id="message" ><button onclick="sendMessageFromClient()">发送消息</button>
</div> <div>
server message list
<ul id="msgList"></ul>
</div> <div>
<button onclick="closeConnection()">关闭连接</button>
</div> <script>
let clientInfo = { userId: 1001, username: '张三' }
clientInfo = JSON.stringify(clientInfo)
let connectionUrl = `ws://localhost:8080/websocket/message/{${clientInfo}}`
console.log(connectionUrl)
var webSocket = null function initConnection() {
webSocket = new WebSocket(connectionUrl) webSocket.onopen = (event) => {
console.log('建立新的WebSocket连接')
} webSocket.onmessage = (event) => {
const message = JSON.stringify(event.data)
console.log(`收到服务端发送的消息,消息内容 -> ${message}`)
const msgList = document.querySelector('#msgList')
msgList.innerHTML += `<li>${message}</li>`
} webSocket.onerror = (error) => {
console.log(`WebSocket连接异常 -> ${JSON.stringify(error)}`)
} webSocket.onclose = (event) => {
console.log(`WebSocket连接关闭 -> ${JSON.stringify(event)}`)
}
} function sendMessageFromClient() {
const message = document.querySelector('#message').value
webSocket.send(JSON.stringify({ message: message }))
} function closeConnection() {
webSocket.close()
} </script>
</body>
</html>

  

  

【WebSocket】一个简单的前后端交互Demo的更多相关文章

  1. 微信小程序 + thinkjs + mongoDB 实现简单的前后端交互

    说明:这段时间跟老师学习了一下mongodb数据库,这次也是第一次搭建后台服务,出了不少差错,特此来复盘一下,非常感谢对我提供帮助的同学~ 一.使用 thinkjs + mongodb 创建后台服务 ...

  2. Node之简单的前后端交互

    node是前端必学的一门技能,我们都知道node是用的js做后端,在学习node之前我们有必要明白node是如何实现前后端交互的. 这里写了一个简单的通过原生ajax与node实现的一个交互,刚刚学n ...

  3. JAVA WebSocKet ( 实现简单的前后端即时通信 )

    1, 前端代码 HTML5 部分 <!DOCTYPE html> <html> <head> <meta charset="utf-8"& ...

  4. 百度ueditor的图片上传,前后端交互使用

    百度ueditor的使用 一个文本编辑器,看了网上很多文档写的很乱,这里拾人牙慧,整理下怎么使用. 这个东西如果不涉及到图片附件上传,其实很简单,就是几个前端文件,直接引用,然后配置下ueditor. ...

  5. 三、vue前后端交互(轻松入门vue)

    轻松入门vue系列 Vue前后端交互 六.Vue前后端交互 1. 前后端交互模式 2. Promise的相关概念和用法 Promise基本用法 then参数中的函数返回值 基于Promise处理多个A ...

  6. 【开源.NET】 轻量级内容管理框架Grissom.CMS(第二篇前后端交互数据结构分析)

    这是 CMS 框架系列文章的第二篇,第一篇开源了该框架的代码和简要介绍了框架的目的.作用和思想,这篇主要解析如何把sql 转成标准 xml 配置文件和把前端post的增删改数据规范成方便后台解析的结构 ...

  7. Servlet实现前后端交互的原理及过程解析

    在日常调试项目时,总是利用tomcat去启动项目,并进行前后端联调,但对于前后端的请求响应的交互原理及过程并不是特别清晰. 为什么在前端发出相应请求,就能跳转到后端通过程序得到结果再响应到前端页面呢? ...

  8. nodejs实现前后端交互

    本人nodejs入门级选手,站在巨人(文殊)的肩膀上学习了一些相关知识,有幸在项目中使用nodejs实现了前后端交互,因此,将整个交互过程记录下来,方便以后学习. 本文从宏观讲述nodejs实现前后端 ...

  9. web前后端交互,nodejs

    手机赚钱怎么赚,给大家推荐一个手机赚钱APP汇总平台:手指乐(http://www.szhile.com/),辛苦搬砖之余用闲余时间动动手指,就可以日赚数百元 web前后端交互 前后端交互可以采用混合 ...

  10. Python 利用三个简易模块熟悉前后端交互流程

    准备工作 在学习Django之前,先动手撸一个简单的WEB框架来熟悉一下前后端交互的整体流程 本次用到的模块: 1.wsgiref,这是一个Python自带的模块,用于构建路由与视图 2.pymysq ...

随机推荐

  1. Java中File类和I/O

    目录 File 类 File 构造方法 输入输出(I/O) 字节流与字符流 输入流与输出流 输入输出字节流 构造方法 方法 InputStream 基本方法 public int read() thr ...

  2. Java中对的创建与引用

    对象与引用 Java语言中除了基本数据类型以外都属于引用类型 Java中的对象是通过引用对其操作的 class Car{ String name; String color; int price; } ...

  3. jquery的全局函数 多库并存

            // jQuery的全局函数 , 也称钩子函数         // 所谓的钩子函数 是 与 其他函数绑定的函数         // 作用是 监听 函数的执行 当函数执行到某个状态时 ...

  4. xpath提取不到值(iframe嵌套)的问题

    爬取http://xgj.xiangyang.gov.cn/zwgk/gkml/?itemid=2471的时候遇到frame嵌套,内部的a标签获取不到. 网上也有人遇到了同样的问题.https://b ...

  5. ETL工具-nifi干货系列 第一讲 揭开nifi神秘面纱

    1.nifi简介 Apache NiFi 是基于流程编程概念的数据流系统.它支持强大且可扩展的数据路由.转换和系统中介逻辑的有向图.NiFi具有基于Web的用户界面,用于设计.控制.反馈和监控数据流. ...

  6. AlertManager解析:构建高效告警系统

    本文深入探讨了AlertManager的技术细节和实际应用,从基本概念.核心组件.工作流程,到与Prometheus的集成和实战案例,旨在为专业人士提供一个全面的AlertManager技术和应用指南 ...

  7. redis zset 延迟合并任务处理

    redis zset 延迟合并任务处理 @Autowired public RedisTemplate redisTemplate; ##1.发送端:在接口中收集任务ID,累计时间段之后,合并处理. ...

  8. 小米便签AS部署之Git的基本使用

    1 项目测试截图 及仓库地址 https://gitee.com/magicfatblink/Notes-master 2 小米便签代码的移植 2.1 IDE 的准备 2.1.1 AS版本选择 由于小 ...

  9. 一行超长日志引发的 “血案” - Containerd 频繁 OOM 背后的真相

    案发现场:混沌初现 2024年6月10日,本应是平静的一天.但从上午 9 点开始,Sealos 公有云的运维监控告警就开始不停地响.北京可用区服务器节点突然出现大量 "not ready&q ...

  10. Bike Sharing Analysis(二)- 假设检验方法

    假设检验 假设检验是推论统计学(inferential statistics)的一个分支,也就是对一个较小的.有代表性的数据组(例如样本集合)进行分析与评估,并依此推断出一个大型的数据组(例如人口)的 ...