spring websocket
spring4 使用websocket
要了解的内容:
sockjs,对于低版本的ie等不支持websocket的浏览器,采用js模拟websocket对象的办法来实现兼容(其实也有轮询的情况)。sockjs地址 https://github.com/sockjs/sockjs-client
stomp 协议,一种格式比较简单且被广泛支持的通信协议,spring4提供了以stomp协议为基础的websocket通信实现。
------------------------------------------------------------------------------------------
然后,重点来了,spring实现websocket的大概原理是什么样子的呢?spring 的websocket实现,实际上是一个简易版的消息队列(而且是主题-订阅模式的),对于要发给具体用户的消息,spring的实现是创建了一个跟用户名相关的主题,实际上如果同一用户登录多个客户端,每个客户端都会收到消息,因此可以看出来,spring的websocket实现是基于广播模式的,要实现真正的单客户端用户区分(单用户多端登录只有一个收到消息),只能依赖于session(相当于一个终端一个主题了)。消息的具体处理过程如何,先上一图:
客户端发送消息,服务端接收后先判断该消息是否需要经过后台程序处理,也就是是否是application消息(图中的/app分支),如果是,则根据消息的url路径转到controller中相关的处理方法进行处理,处理完毕后发送到具体的主题或者队列;如果不是application消息,则直接发送到相关主题或者队列,然后经过处理发送给客户端。
因此在使用的时候,有了一开始的客户端注册到指定url,这个所谓的注册到执行url的过程,实际就是客户端跟服务端建立websocket连接的过程,连接建立之后,要发送或者接收什么消息都通过这一个websocket通信连接来完成,而不是每一个主题建立一个连接,这样可以节省开销。其中服务端代码.withSockJS()的作用是声明我们想要使用 SockJS 功能,如果WebSocket不可用的话,会使用 SockJS。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@Configuration @EnableWebSocketMessageBroker //在 WebSocket 上启用 STOMP public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //webServer就是websocket的端点,客户端需要注册这个端点进行链接, registry.addEndpoint( "/webServer" ).setAllowedOrigins( "*" ).withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // registry.setPathMatcher(new AntPathMatcher("."));//可以已“.”来分割路径,看看类级别的@messageMapping和方法级别的@messageMapping registry.enableSimpleBroker( "/topic" , "/user" ); registry.setUserDestinationPrefix( "/user/" ); registry.setApplicationDestinationPrefixes( "/app" ); //走@messageMapping } @Override public boolean configureMessageConverters(List<MessageConverter> messageConverters) { return true ; } @Override public void configureWebSocketTransport(WebSocketTransportRegistration webSocketTransportRegistration) { } @Override public void configureClientInboundChannel(ChannelRegistration channelRegistration) { } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { // TODO Auto-generated method stub } @Override public void addArgumentResolvers(List<org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver> list) { } @Override public void addReturnValueHandlers(List<org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler> list) { } } |
@EnableWebSocketMessageBroker 的作用是在WebSocket 上启用 STOMP,registerStompEndpoints方法的作用是websocket建立连接用的(也就是所谓的注册到指定的url),configureMessageBroker方法作用是配置一个简单的消息代理。如果补充在,默认情况下会自动配置一个简单的内存消息队列,用来处理“/topic”为前缀的消息,但经过重载后,消息队列将会处理前缀为“/topic”、“/user”的消息,并会将“/app”的消息转给controller处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@RequestMapping ( "/myws" ) @Controller public class WebSocketController { @Resource private SimpMessagingTemplate simpMessagingTemplate; @MessageMapping ( "/hello" ) // @SendTo("/topic/hello")//会把方法的返回值广播到指定主题(“主题”这个词并不合适) public void toTopic(SocketMessageVo msg , String name) { System.out.println(msg.getName()+ "," +msg.getMsg()); this .simpMessagingTemplate.convertAndSend( "/topic/hello" ,msg.getName()+ "," +msg.getMsg()); // return "消息内容:"+ msg.getName()+"--"+msg.getMsg(); } @MessageMapping ( "/message" ) // @SendToUser("/message")//把返回值发到指定队列(“队列”实际不是队列,而是跟上面“主题”类似的东西,只是spring在SendTo的基础上加了用户的内容而已) public void toUser(SocketMessageVo msg ) { System.out.println(msg.getName()+ "," +msg.getMsg()); this .simpMessagingTemplate.convertAndSendToUser( "123" , "/message" ,msg.getName()+msg.getMsg()); } @RequestMapping ( "/sendMsg" ) public void sendMsg(HttpSession session){ System.out.println( "测试发送消息:随机消息" +session.getId()); this .simpMessagingTemplate.convertAndSendToUser( "123" , "/message" , "后台具体用户消息" ); } } |
WebSocketConfig 中配置setApplicationDestinationPrefixes()的消息会被转发到WebSocketController 中 @MessageMapping 相应方法进行处理。@SendTo("/topic/hello") 会把方法的返回值序列化为json串,然后发送到指定的主题,不用此注解,使用 simpMessagingTemplate.convertAndSend 效果相同;若为 @SendToUser("/message") 则为发送到指定的用户队列(实际队列名字为/user/用户名/原队列名),不用此注解,使用 simpMessagingTemplate.convertAndSendToUser() 效果相同;
后台主动往前端推送消息,直接调用 simpMessagingTemplate.convertAndSendToUser() 跟 simpMessagingTemplate.convertAndSend() 即可将消息发往队列或者主题。
前端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//建立websocket连接 function openWs(){ websocket = new SockJS( "http://localhost:8080/autotest" + "/webServer" ); var stompClient = Stomp.over(websocket); stompClient.connect({}, function (frame) { stompClient.subscribe( '/topic/hello' , function (data) { //订阅消息 console.log( "收到topic消息:" +data.body); //body中为具体消息内容 }); stompClient.subscribe( '/user/' + 123 + '/message' , function (message){ console.log( "收到session消息:" +message.body); //body中为具体消息内容 }); }); document.getElementById( "sendws" ).onclick = function () { stompClient.send( "/app/message" , {}, JSON.stringify({ name: "nane" , msg: "发送的消息aaa" })); } } //关闭连接 function wsClose() { websocket.close(); } |
代码完成后运行效果如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
2021-01-21 springcloud python 注册到 eureca/gateway