赞
踩
1. 什么是WebSocket?
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:
WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;
WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。
2. WebSocket的特点
STOMP协议相当于Websocket的子协议。
STOMP 中文为: 面向消息的简单文本协议
websocket定义了两种传输信息类型:文本信息和二进制信息。类型虽然被确定,但是他们的传输体是没有规定的。所以,需要用一种简单的文本传输类型来规定传输内容,它可以作为通讯中的文本传输协议。
STOMP是基于帧的协议,客户端和服务器使用STOMP帧流通讯
一个STOMP客户端是一个可以以两种模式运行的用户代理,可能是同时运行两种模式。
作为生产者,通过SEND框架将消息发送给服务器的某个服务
作为消费者,通过SUBSCRIBE制定一个目标服务,通过MESSAGE框架,从服务器接收消息。
例如:
COMMAND
header1:value1
header2:value2
Body^@
注:帧以commnand字符串开始,以EOL结束。其中包括可选回车符(13字节),紧接着是换行符(10字节)。command下面是0个或多个:格式的header条目, 每个条目由EOL结束。一个空白行(即额外EOL)表示header结束和body开始。body连接着NULL字节。本文档中的例子将使用^@代表NULL字节。NULL字节可以选择跟多个EOLs。欲了解更多关于STOMP帧的详细信息,请参阅STOMP1.2协议规范。
微信小程序提供的关于WebSocket的API不是基于STOMP协议的,只能用websocket实现,当然可以对微信小程序的前端进行更改,使其可以使用基于STOMP协议的后端实现。
基于STOMP协议的websocket实现可以参考:Java实战:Spring Boot实现WebSocket实时通信
<dependencies>
<!-- Spring Boot Web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot WebSocket依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
package com.lixy.sharingcurriculum.config; import com.lixy.sharingcurriculum.controller.CommentController; import com.lixy.sharingcurriculum.service.CommentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { /* 使用springboot内置tomcat需要该bean,打war包则注释掉该bean */ @Bean public ServerEndpointExporter serverEndpoint() { return new ServerEndpointExporter(); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //注册一个WebSocket端点,路径为"/ws" //registry.addEndpoint("/ws").withSockJS(); //客户端调用的URL; registry.addEndpoint("/ws").setAllowedOrigins("*"); } //因为websocket中不能直接使用@Autowired注入Service,添加下面配置 在socket引入Service @Autowired public void socketUserService(CommentService commentService){ CommentController.commentService = commentService; } }
创建一个WebSocket的配置类WebSocketConfig
实现WebSocketMessageBrokerConfigurer
接口,需要手动注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。
我的理解是为了将WebSocket与Spring容器整合,通过在配置类中添加@Bean,使其返回对象ServerEndpointExporter,意味着将该对象纳入spring框架的管理中,spring容器将能够处理WebSocket相关的配置和依赖。
registry.addEndpoint("/ws").setAllowedOrigins("*");
意思是允许/ws/*
的所有请求访问。websocket默认禁止了非同源访问
,如果没有加入自己的跨域配置或这个配置的话,前端将会报403错误。参考:客户端连接spring websocket 返回403错误
因为我需要同时对数据库进行修改,所以引入了commentService进行数据相关操作。
同源访问指的是WebSocket连接的客户端和服务器端拥有相同的协议、主机和端口。
在Web开发中,同源策略是一种安全机制,它要求前端的网页脚本只能与同源的服务器进行交互。具体来说,同源访问要求客户端的页面和服务器端的服务必须使用相同的协议(如HTTP或HTTPS)、相同的主机名(如www.example.com)以及相同的端口号(如80或443)。如果这些条件中有任何一个不匹配,那么就是非同源访问。
对于WebSocket而言,虽然它本身并不强制执行同源策略,但浏览器的安全限制通常要求WebSocket遵循同源策略。这意味着默认情况下,一个网页中的WebSocket脚本只能连接到与其同源的服务器。如果需要连接到不同源的服务器,可以使用CORS(跨域资源共享)来允许这种跨域访问。
总结来说,同源访问是WebSocket通信的一种默认安全限制,它要求客户端和服务器在协议、主机和端口上保持一致。如果需要进行非同源访问,开发者需要在服务器端配置相应的CORS策略,以允许跨域的WebSocket连接。
webSocket接收和发送的消息的类型只能是String类型,所以在传数据的时候需要做相应的处理。
package com.lixy.sharingcurriculum.controller; import com.alibaba.fastjson.JSON; import com.lixy.sharingcurriculum.entity.Message; import com.lixy.sharingcurriculum.entity.dto.MessDto; import com.lixy.sharingcurriculum.service.CommentService; import jakarta.websocket.*; import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component @ServerEndpoint("/ws/{courid}/{userid}") @Slf4j public class CommentController { //存储被课程id和正在讨论区中的用户id和对应会话 public static final Map<String, Map<String, Session>> sessionMap = new ConcurrentHashMap<>(); public static CommentService commentService; /*** * 建立连接时调用 * 1.第一层map存储被课程id和浏览的用户及会话 * 2.第二层map存储用户id及会话 */ @OnOpen public void onOpen(Session session, @PathParam("courid") String courid,@PathParam("userid") String userid){ log.info("用户{}打开了课程{}的讨论区",userid,courid); if(sessionMap.containsKey(courid)){ sessionMap.get(courid).put(userid,session); }else { Map<String,Session> map = new HashMap<>(); map.put(userid,session); sessionMap.put(courid,map); } log.info(String.valueOf(sessionMap)); } /** * 接收处理客户端发来的数据 */ @OnMessage public void onMessage(String message) { // 解析消息为java对象 Message msg = JSON.parseObject(message, Message.class); log.info("msg:"+msg); if(msg != null&& msg.getContent() != null &&!msg.getContent().isEmpty()){ //将评论内容存入数据库 MessDto messDto=commentService.saveComment(msg); //将评论内容发送给其他正在浏览该文章的用户 sendAllMessage(messDto); }else{ System.out.println("评论内容为空"); } } //服务端发送消息给客户端 private void sendAllMessage(Message message) { log.info("sendAllMessage:{}",message); try { String courid = message.getCourid(); log.info("courid:{}",courid); //根据评论发送课程的id,将该评论发送给正在讨论区中的用户 Map<String,Session> passageMap = sessionMap.get(courid); for (Session session : passageMap.values()) { session.getBasicRemote().sendText(JSON.toJSONString(message)); } } catch (Exception e) { e.printStackTrace(); } } //关闭连接 /** * 1.把登出的用户从sessionMap中剃除 * 2.发送给所有人当前登录人员信息 */ @OnClose public void onClose(@PathParam("courid") String courid,@PathParam("userid") String userid) { //用户退出讨论区,则将该用户及其会话移除 Map<String,Session> passageMap = sessionMap.get(courid); passageMap.remove(userid); //没有用户在讨论区中,则将课程id从map中移除 if(passageMap.isEmpty()){ sessionMap.remove(courid); } } @OnError public void onError(Session session, Throwable error) { System.out.println("发生错误"); error.printStackTrace(); } }
//创建WebSocket连接 createWebSocket(){ const websocket =wx.connectSocket({ url: 'ws://localhost:8080/ws/'+this.data.courid+"/"+this.data.userid, success: () => { console.log('WebSocket连接成功'); }, fail: (error) => { console.log('WebSocket连接失败', error); } }) // 监听WebSocket连接打开事件 websocket.onOpen(() => { console.log('WebSocket连接已打开'); console.log('readyState:', websocket.readyState); // 应该输出: OPEN (1) }); // 监听WebSocket接收到服务器消息事件 websocket.onMessage((message) => { console.log('收到服务器消息', message); // 处理服务器返回的消息,例如更新页面数据等操作 var comment=JSON.parse(message.data) comment.createtime=this.timestampToDate(comment.createtime) var comment_list=this.data.comment_list; comment_list.push(comment) this.setData({ comment_list }) }); // 监听WebSocket连接关闭事件 websocket.onClose(() => { console.log('WebSocket连接已关闭'); }); // 监听WebSocket错误事件 websocket.onError((error) => { console.log('WebSocket发生错误', error); }); // 将WebSocket对象保存到data中 this.setData({ websocket: websocket, }); }, // 发送消息到WebSocket服务器 if (this.data.websocket && this.data.websocket.readyState === 1) { this.data.websocket.send({ data: JSON.stringify(message), // 要发送的消息内容 success: () => { console.log('消息发送成功'); }, fail: (error) => { console.log('消息发送失败', error); }, }); } else { console.log('WebSocket连接未打开或不存在'); }
Java实战:Spring Boot实现WebSocket实时通信
WebSocket和Stomp协议
个人博客——使用websocket实现评论实时展示
客户端连接spring websocket 返回403错误
微信小程序 内容评论-回复评论-回复回复的实现
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。