【WebSocket】一个简单的前后端交互Demo
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的更多相关文章
- 微信小程序 + thinkjs + mongoDB 实现简单的前后端交互
说明:这段时间跟老师学习了一下mongodb数据库,这次也是第一次搭建后台服务,出了不少差错,特此来复盘一下,非常感谢对我提供帮助的同学~ 一.使用 thinkjs + mongodb 创建后台服务 ...
- Node之简单的前后端交互
node是前端必学的一门技能,我们都知道node是用的js做后端,在学习node之前我们有必要明白node是如何实现前后端交互的. 这里写了一个简单的通过原生ajax与node实现的一个交互,刚刚学n ...
- JAVA WebSocKet ( 实现简单的前后端即时通信 )
1, 前端代码 HTML5 部分 <!DOCTYPE html> <html> <head> <meta charset="utf-8"& ...
- 百度ueditor的图片上传,前后端交互使用
百度ueditor的使用 一个文本编辑器,看了网上很多文档写的很乱,这里拾人牙慧,整理下怎么使用. 这个东西如果不涉及到图片附件上传,其实很简单,就是几个前端文件,直接引用,然后配置下ueditor. ...
- 三、vue前后端交互(轻松入门vue)
轻松入门vue系列 Vue前后端交互 六.Vue前后端交互 1. 前后端交互模式 2. Promise的相关概念和用法 Promise基本用法 then参数中的函数返回值 基于Promise处理多个A ...
- 【开源.NET】 轻量级内容管理框架Grissom.CMS(第二篇前后端交互数据结构分析)
这是 CMS 框架系列文章的第二篇,第一篇开源了该框架的代码和简要介绍了框架的目的.作用和思想,这篇主要解析如何把sql 转成标准 xml 配置文件和把前端post的增删改数据规范成方便后台解析的结构 ...
- Servlet实现前后端交互的原理及过程解析
在日常调试项目时,总是利用tomcat去启动项目,并进行前后端联调,但对于前后端的请求响应的交互原理及过程并不是特别清晰. 为什么在前端发出相应请求,就能跳转到后端通过程序得到结果再响应到前端页面呢? ...
- nodejs实现前后端交互
本人nodejs入门级选手,站在巨人(文殊)的肩膀上学习了一些相关知识,有幸在项目中使用nodejs实现了前后端交互,因此,将整个交互过程记录下来,方便以后学习. 本文从宏观讲述nodejs实现前后端 ...
- web前后端交互,nodejs
手机赚钱怎么赚,给大家推荐一个手机赚钱APP汇总平台:手指乐(http://www.szhile.com/),辛苦搬砖之余用闲余时间动动手指,就可以日赚数百元 web前后端交互 前后端交互可以采用混合 ...
- Python 利用三个简易模块熟悉前后端交互流程
准备工作 在学习Django之前,先动手撸一个简单的WEB框架来熟悉一下前后端交互的整体流程 本次用到的模块: 1.wsgiref,这是一个Python自带的模块,用于构建路由与视图 2.pymysq ...
随机推荐
- MySQL学习笔记-数据操作语言
SQL-数据操作语言(DML) 数据操作语言,用于对数据库中表的数据记录进行增删改的操作 一.添加数据(insert) 1. 给指定字段添加数据 insert into {表名} ({字段1},{字段 ...
- 剑指Offer-59.按之字形顺序打印二叉树(C++/Java)
题目: 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. 分析: 实际上是二叉树的层次遍历,只不过每一行 ...
- 剑指Offer-53.表示数值的字符串(C++/Java)
题目: 请实现一个函数用来判断字符串是否表示数值(包括整数和小数).例如,字符串"+100","5e2","-123","3.14 ...
- The requested operation cannot be completed because the connection has been broken
具体报错 The requested operation cannot be completed because the connection has been broken. -- xxxForyy ...
- 什么是JDBC,在上面时候会用到它?
JDBC的全称是Java DataBase Connection,也就是Java数据库连接,我们可以用它来操作关系型数据库.JDBC接口及相关类在java.sql包和javax.sql包里.我们可以用 ...
- BC5-牛牛学说话之-字符
题目描述 会说浮点数之后,牛牛开始尝试字符.输入一个字符,输出这个字符. 输入描述 输入一个字符,范围在 ascii 范围内 输出描述 输出这个字符 示例 1 输入:a 输出:a 解题思路 方案一 字 ...
- navicat 15
新机经常需要安装navicat每次都要去网上搜很麻烦这次搜到了记录下来以后就不需要重复下载了 有X86和X64两个版本 破解说明在README文档中 下载地址:https://www.aliyundr ...
- 【踩坑】.NET 8.0 自定义IExceptionHandler不生效
中间件实现异常处理 在ASP.NET Core里,我们可以使用中间件(Middleware)实现全局的异常处理. 如内置的异常处理中间件 UseExceptionHandler app.UseExce ...
- C#拾贝
C#拾贝 C#C#技巧C#进阶 不积跬步无以至千里,不积小流无以成江河 C#拾贝 一.Linq 1.以...开头 StartsWith Repeater1.DataSource=con.Users.W ...
- Kotlin 编程语言详解:特点、应用领域及语法教程
什么是 Kotlin? Kotlin 是一种现代.流行的编程语言,由 JetBrains 在 2016 年发布. 自发布以来,它已经变得非常流行,因为它与 Java 兼容(Java 是目前最流行的编程 ...