SpringBoot+WebSocket实现即时通讯的方法详解

网友投稿 1100 2022-10-05

SpringBoot+WebSocket实现即时通讯的方法详解

目录环境信息服务端实现导入依赖创建配置类创建一个注解式的端点并在其中通过配套注解声明回调方法服务端主动发送消息给客户端客户端实现java客户端实现在前端环境(vue)中使用websocket

环境信息

名称版本号Spring Boot2.4.5Idea2021.3.2

服务端实现

导入依赖

org.springframework.boot

spring-boot-starter-websocket

注意:Spring Boot在父工程中已经管理了websocket的版本信息,所以不用指定版本号也是可以的

创建配置类

package com.fenzhichuanmei.configuration;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**

* @author Yi Dai 484201132@qq.com

* @since 2022/5/13 11:34

*/

@Configuration

public class WebsocketConfiguration {

@Bean

public ServerEndpointExporter serverEndpointExporter() {

return new ServerEndpointExporter();

}

}

创建此配置类的目的只是为了把ServerEndpointExporter 这个类的实例交给spring 容器进行管理,您可以用任意一种方式交给容器,如使用@Import(ServerEndpointExporter.class)这种方式等进行操作;此处只是我的编码风格如此;并非必须这样操作

创建一个注解式的端点并在其中通过配套注解声明回调方法

package com.fenzhichuanmei.websocket;

import com.fenzhichuanmei.websocket.utils.SessionManager;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

import javax.websocket.*;

import javax.websocket.server.PathParam;

import javax.websocket.server.ServerEndpoint;

/**

* @author Yi Dai 484201132@qq.com

* @since 2022/3/7 15:47

*/

@Slf4j

@Component

@ServerEndpoint("/arcticFoxServerEndpoint/{websocketClientType}")

public class ArcticFoxServerEndpoint {

private static SessionManager sessionManager;

@Resource

public void setProcessor(SessionManager sessionManager) {

ArcticFoxServerEndpoint.sessionManager = sessionManager;

}

/**

* 建立连接成功的回调方法

*

* @param session 会话对象

* @param websocketClientType 此参数就是路径中{websocketClientType}位置传入的参数

*/

@OnOpen

public void onOpen(Session session, @PathParam("websocketClientType") int websocketClientType) {

sessionManager.onOpen(session, websocketClientType);

}

/**

* 当会话关闭时执行的回调方法

*

* @param session 会话对象

* @param websocketClientType 此参数就是路径中{websocketClientType}位置传入的参数

*/

@OnClose

public void onClose(Session session, @PathParam("websocketClientType") int websocketClientType) {

sessionManager.onClose(session, websocketClientType);

}

/**

* 当收到客户端信息时执行的回调方法

*

* @param session 会话对象

* @param message 客户端传递过来的信息

* @param websocketClientType 此参数就是路径中{websocketClientType}位置传入的参数

*/

@OnMessage

public void onMessage(Session session, String message, @PathParam("websocketClientType") int websocketClientType) {

sessionManager.onMessage(session, message, websocketClientType);

}

/**

* 当发生错误时的回调方法

*

* @param session 会话对象

* @param e 异常对象

* @param websocketClientType 此参数就是路径中{websocketClientType}位置传入的参数

*/

@OnError

public void onError(Session session, Throwable e, @PathParam("websocketClientType") int websocketClientType) {

sessionManager.onError(session, e, websocketClientType);

}

}

@ServerEndpoint注解标注此类为一个服务端的端点类,此注解有一个必须的参数,用于指定客户端访问的地址,本案例中为:/arcticFoxServerEndpoint,而路径后面的/{websocketClientType}这个是路径中参数的占位符,有点类似与Spring Mvc中Rest接口和@PathVariable注解的作用

注意事项: 一定要将此类交给spring 容器进行管理!!还有一个坑就是,此类的实例时非单例的,所以如果要在此类中注入其他的bean,不能使直接在属性上使用@Resource注解或者@Autowired等注解进行注入,否则会报错。正确操作应该是把要注入的字段设置为静态的,然后通过非静态的set方法进行注入,具体代码请看上方实例

服务端主动发送消息给客户端

通过上面的代码我们可以知道每个回调方法中都会收到一个Session对象,正如您所想,要向客户端发送消息正是要借助此对象;Session对象有一个getAsyncRemote方法,调用此方法可以得到一个RemoteEndpoint.Async对象,查看此对象,发现有很多send打头的方法;

是的,这些方法就是发送消息的方法,博主这个项目中主要是通过jsON来进行交互的,所以我使用了sendText方法,示例代码:

RemoteEndpoint.Async asyncRemote = session.getAsyncRemote();

asyncRemote.sendText(jsonString);

很显然中转变量asyncRemote 没什么太大的用处,不如直接写成:

session.getAsyncRemote().sendText(jsonString);

通过方法名看到,似乎还可以发送对象,二进制序列等,博主没有深入研究,有兴趣的小伙伴可以尝试尝试

客户端实现

一般来讲客户端应该是用Java Script实现,但是博主这个项目比较特殊,需要用Java来实现客户端,下面博主先以Java客户端说明其实现细节,然后再说再前端如何实现

Java客户端实现

导入依赖

org.java-websocket

Java-WebSocket

1.5.3

其实Java中实现WebSocket的第三方包还有很多,博主这个地方使用的是Java-WebSocket,有兴趣的小伙伴可以试试其他的包

建立连接和处理回调

package com.fenzhichuanmei.websocket;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fenzhichuanmei.components.PaymentComponent;

import com.fenzhichuanmei.pojo.Instructions;

import com.fenzhichuanmei.utils.WebsocketClientType;

import lombok.Data;

import lombok.extern.slf4j.Slf4j;

import org.java_websocket.client.WebSocketClient;

import org.java_websocket.enums.ReadyState;

import org.java_websocket.handshake.ServerHandshake;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

import java.net.URI;

import java.net.URISyntaxException;

/**

* @author Yi Dai 484201132@qq.com

* @since 2022/5/13 10:16

*/

@Slf4j

@Component

public class ArcticFoxWebSocketClient {

@Resource

private ObjectMapper objectMapper;

@Resource

private PaymentComponent paymentComponent;

@Resource

private ArcticFoxWebSocketClientProperties properties;

public void establishConnection() throws URISyntaxException {

WebSocketClient webSocketClient = new WebSocketClient(new URI(String.format("%s/%d", properties.getWebSocketServerUrl(), WebsocketClientType.PAYMENT_DEVICE))) {

@Override

public void onOpen(ServerHandshake serverHandshake) {

log.info("WebSocketClient: onOpen : {}", serverHandshake);

}

@Override

public void onMessage(String jsonString) {

try {

Instructions instructions = objectMapper.readValue(jsonString, Instructions.class);

if (instructions.getType() == Instructions.NOTICE_PAYMENT) {

paymentComponent.queryAnUnpaidOrdersAndPay();

} else {

throw new RuntimeException("错误的指令类型");

}

} catch (JsonProcessingException e) {

e.printStackTrace();

}

}

@Override

public void onClose(int i, String s, boolean b) {

log.info("WebSocketClient: onClose : i:{},s:{},b:{}", i, s, b);

try {

Thread.sleep(1000 * 20);

establishConnection();

} catch (InterruptedException | URISyntaxException e) {

e.printStackTrace();

}

}

@Override

public void onError(Exception e) {

log.error("WebSocketClient: onError {}", e.getMessage());

}

};

webSocketClient.connect();

while (!(webSocketClient.getReadyState() == ReadyState.OPEN)) {

try {

Thread.sleep(1000 * 2);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

log.info("WebSocketClient: connection established successfully");

}

@Data

@Component

@ConfigurationProperties("arctic-fox-web-socket-client.properties")

public static class ArcticFoxWebSocketClientProperties {

oKNhTegvWprivate String webSocketServerUrl;

}

}

代码解释: 其实我的establishConnection方法中上来就实例化了一个WebSocketClient 类的实例,请注意,此类是个抽象类,我在这里用匿名实现类的方式实现的,此类有几个抽象方法需要实现,也就是onOpen,onMessage,onClose,onError四个方法,其作用其实已经是很见名知意了,和服务端的回调方法一样,就不过多解释;实例化此类需要传入一个URI对象,这个URI对象其实就是封装了对服务端连接的地址,由于博主不希望把服务端的地址给写死了,所以我配置到了配置文件中,然后通过String.format静态方法配合占位符拼接url地址和参数;路径的规则是:协议名://IP地址(或域名):端口号/服务端声明的地址/参数;举个例子:

ws://192.168.88.88:8080/arcticFoxServehttp://rEndpoint/1

ws://localhost:8080/arcticFoxServerEndpoint/2

ws://为协议;实例化WebSocketClient 类的实例之后,调用其connect()方法即开始建立连接,调用getReadyState()方法可以获得其状态;由于我的服务端可能随时都连不上,所以我在客户端的onClose回调函数中进行了一个递归(20秒后),用于重新连接。

客户端向服务端发送消息

通过WebSocketClient 类的实例,我们可以看到有以下方法,很明显send方法就是用来发送消息使用的

示例代码:

//判断一下是否为空

if (Objects.nonNull(webSocketClient)) {

try {

//通过jackson将对象转换为json字符串(非必须)

String jsonString = objectMapper.writeValueAsString(feedback);

//发送信息

webSocketClient.send(jsonString);

} catch (JsonProcessingException e) {

e.printStackTrace();

}

} else {

log.warn("no connection established");

}

在前端环境(vue)中使用websocket

安装reconnecting-websocket包(非必须)

npm i --save reconnecting-websocket

安装这个包是为了websocket能在断线之后重新连接,其实不使用这个包也是可以用原生Java Script实现的;但是他和原生的api几乎一样;

示例代码:

import ReconnectingWebSocket from "reconnecting-websocket";

export default function initializationWebsocket() {

let reconnectingWebSocket = new ReconnectingWebSocket(`ws://localhost:8080/arcticFoxServerEndpoint/${2}`);

reconnectingWebSocket.onopen = event => {

console.log("on open :", event);

};

reconnectingWebSocket.onmessage = event => {

//event对象中data存储的就是服务端发送过来的消息

let parse = JSON.parse(event.data);

console.log("webSocket on message :", parse);

};

reconnectingWebSocket.onclose = event => {

console.log(event);

};

reconnectingWebSocket.onerror = event => {

console.log(event);

};

//窗口关闭时断开连接

window.onbeforeunload = function () {

reconnectingWebSocket.close();

}

}

在前端中实现websocket就比较简单了,就上面的几行代码即可,不用调用其他函数进行连接,实例化之后就开始连接了

想服务端发送信息

在前端中发送信息就更简单了,直接调用reconnectingWebSocket的send方法,传入要发送的数据即可

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇: 微信小程序实例:如何引入外部js的文件(图文)
下一篇: 定时备份mysql 到本地,并且发送备份到邮件 mutt
相关文章

 发表评论

取消回复

暂时没有评论,来抢沙发吧~

深圳SEO优化公司铁岭百度网站优化多少钱廊坊网站优化推广价格贵港seo网站优化哪家好绵阳百度网站优化排名价格武汉企业网站设计哪家好金昌网站推广报价罗湖百度网站优化价格亳州关键词排名包年推广贵阳网站改版推荐张掖网络营销推荐济宁如何制作网站价格思茅优秀网站设计公司北海企业网站建设公司晋城至尊标王哪家好陇南网络营销多少钱承德设计网站报价抚顺优化推荐黄南seo网站优化推荐通辽网络营销哪家好眉山百度竞价包年推广报价永新外贸网站制作天津网站制作多少钱霍邱网站优化按天扣费推荐潜江百姓网标王价格平顶山百度爱采购哪家好庆阳设计公司网站哪家好天门网页制作报价潜江网站搭建推荐百色模板推广价格通辽seo网站优化歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化