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. 实例讲解多处理器下的计算机启动(xv6的启动过程)

    启动 启动方面的文章之前也写过,那是我的第一篇文章,本文在前文的基础之上完善,然后增加了多处理器启动的情况,废话不多说直接来看. 启动可以分为两种,一种为冷启动,是指计算机在关机状态下按 POWER ...

  2. 配置系统未能初始化。“System.Transactions.Diagnostics.DiagnosticTrace”的类型初始值设定项引发异常。

    配置系统未能初始化."System.Transactions.Diagnostics.DiagnosticTrace"的类型初始值设定项引发异常. 1.是检查当前程序的 App.c ...

  3. 如何排查常规软件问题 - 面向 Linux 初级用户的教程

    笔者从 14 年做开源软件以来,接触了众多 Linux 新手用户,这里我为这类用户总结了一些常见的问题排查方法,希望能帮助到大家.如果你已经工作多年,对于下面提到的思路和方法应该非常熟悉,如果对某一条 ...

  4. Redis的特点什么是?

    a.支持多种数据结构,如 string(字符串). list(双向链表).dict(hash表).set(集合).zset(排序set).hyperloglog(基数估算) b.支持持久化操作,可以进 ...

  5. kettle从入门到精通 第六十九课 ETL之kettle kettle cdc mysql,轻松实现实时增量同步

    1.之前kettle cdc mysql的时候使用的方案是canal+kafka+kettle,今天我们一起学习下使用kettle的插件Debezium直接cdc mysql. 注:CDC (Chan ...

  6. jenkins发布服务失败查看catalina.out启动日志和xxl-job jobhandler naming conflicts

    jenkins发布服务失败查看catalina.out启动日志和xxl-job jobhandler naming conflicts 1.查看tomcat/logs/catalina.out 日志, ...

  7. apollo配置json

    #json串原文[{"username":"李小刚","sex":"男"},{"username": ...

  8. 58同城的登录(RSA算法)

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 58同城的登录(RSA算法) 日期:2016-11-23 ...

  9. Kotlin 数据类型详解:数字、字符、布尔值与类型转换指南

    Kotlin 数据类型 在 Kotlin 中,变量的类型由其值决定: 示例 val myNum = 5 // Int val myDoubleNum = 5.99 // Double val myLe ...

  10. 用cvCvtColor转化RGB彩色图像为灰度图像时发生的小失误

    版本信息 MAC版本:10.10.5 Xcode版本:7.2 openCV版本:2.4.13 在运行程序的时候发现cvCvtColor的地方程序报错 error: (-215) src.depth() ...