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. 代码审计——基础(JAVASE)

    JAVASE 目录 JAVASE 基本语法 关键字 变量 作业1 作业完成 第一题:简单的介绍了java语言历史,优势.发展 第二题:特性:面向对象.跨平台.封装.继承.多态.抽象.扩展性.健壮性.垃 ...

  2. vue3拉取代码install 报错 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: npm ERR! Found: vue@3.2.31

    先看报错 说明安装的包和现有的包已经冲突了版本不一致 我们先试一下npm install @vue/cli -- force然后再试一下npm install @vue/cli --legacy-pe ...

  3. react自定义导航组件 路由参数

    为何需要自定义导航? 因为在项目中往往不是所有的声明式导航都是需要a标签完成,有时候可能需要别的标签,此时如果在需要的地方去写编程式导航就会有代码重复可能性,就在对于公共代码进行提取. 思路: 定义一 ...

  4. CSP-S2019 江西 题解

    为什么有 \(5\) 道题? [CSP-S2019 江西] 和积和 简单化一下式子: \[(n + 1) \times \sum A_i \times B_i - (\sum A_i) \times ...

  5. IDEA:java: Compilation failed: internal java compiler error

    java: Compilation failed: internal java compiler error 解决方法: 1.打开菜单 ,File - Project Structure - Proj ...

  6. RSA算法中,为什么需要的是两个素数?

    PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全.密码学.联邦学习.同态加密等隐私计算领域的技术和内容. RSA算法中,为什么需要的是两个素数? RSA算法是一种广泛使用 ...

  7. work05

    第一题:分析以下需求,并用代码实现 手机类Phone 属性: 品牌brand 价格price 行为: 打电话call() 发短信sendMessage() 玩游戏playGame() 要求: 1.按照 ...

  8. pyenv-win 替换国内镜像源

    前情提要 今天心血来潮想学一学python 然后因为python版本众多,了解到了pyenv这个python版本管理器 在github下载好pyenv以后,打算先安装一个稳定的版本 pyenv ins ...

  9. Java映射 转换post response T data

    Java映射 转换post response data 接上篇Java泛型对象在http请求和响应对象中的封装https://www.cnblogs.com/oktokeep/p/17688322.h ...

  10. mysql case when使用

    ## mysql case when使用 SELECT order_no,case is_test when 0 then '否'when 1 then '是'end as '是否测试' from ` ...