首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

传统@ServerEndpoint方式开发WebSocket应用和SpringBoot构建WebSocket应用程序

  • 25-02-22 00:21
  • 2127
  • 13668
blog.csdn.net
小Hub领读:

通过websocket的两种使用方式,让你更加深入理解用法。很多人不懂websocket怎么辨别是谁发送过来的,文中说到实现WebSocketConfigurer接口,定义拦截器可以绑定用户信息,还有其他很多,细细品,对比看比较好!

文末有源码地址~

对了,看完记得点个[ 在看 ]支持一下哈。


作者:编码妙妙屋

https://www.skypyb.com/2019/02/jishu/java/813/

WebSocket 一次握手就可以使客户端和服务端建立长连接,并进行双向数据传输。

由于其双向传输特性,服务端可主动向客户端发送信息,实时性很高。

而与 HTTP 协议比起来 WebSocket 协议每次数据传输的头信息都较小,节约带宽。

在获取实时数据这方面时,那是比 ajax 轮询方式高到不知道哪去了。

在 SpringBoot 架构下进行 WebSocket 服务开发的话, 首先还是要导入这个

就算是使用 Tomcat 7 的 @ServerEndpoint 进行 WebSocket 开发, 也得导。不然在 SpringBoot 环境下会有 bug。

普通 java web 应用开发 WebSocket 就不需要了,@ServerEndpoint 直接用就行。

compile('org.springframework.boot:spring-boot-starter-websocket:2.0.4.RELEASE')

maven 仓库: spring-boot-starter-websocket

这是使用 @ServerEndpoint 进行的传统 WebSocket 开发:

由于我是在 SpringBoot 环境, 所以得先写个能扫描 @ServerEndpoint 的配置, 不然在客户端连接的时候会一直连不上。不是在 SpringBoot 下开发的可以跳过这一环节。

ServerEndpointExporter 这个类偏偏还是 spring-boot-starter-websocket 提供的。所以必须要导入这个依赖。

  1. package com.skypyb.websocket;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.socket.server.standard.ServerEndpointExporter;
  5. @Configuration
  6. public class WebSocketConfigOne {
  7. /**
  8. * 这个bean会自动注册使用了@ServerEndpoint注解声明的对象
  9. * 没有的话会报404
  10. *
  11. * @return
  12. */
  13. @Bean
  14. public ServerEndpointExporter serverEndpointExporter() {
  15. return new ServerEndpointExporter();
  16. }
  17. }

@ServerEndpoint 注解中写上客户端连接的地址。

在类层次上, 还得加上 @Component 注解才行。普通 java web 项目不用加。

这个方式开发的 WebSocket 服务器,每个连接加入都会为该连接新建一个服务器对象绑定。

  1. package com.skypyb.websocket;
  2. import org.springframework.stereotype.Component;
  3. import javax.websocket.*;
  4. import javax.websocket.server.ServerEndpoint;
  5. import java.io.IOException;
  6. import java.util.concurrent.CopyOnWriteArraySet;
  7. /**
  8. * @ServerEndpoint 该注解可以将类定义成一个WebSocket服务器端,
  9. * @OnOpen 表示有浏览器链接过来的时候被调用
  10. * @OnClose 表示浏览器发出关闭请求的时候被调用
  11. * @OnMessage 表示浏览器发消息的时候被调用
  12. * @OnError 表示报错了
  13. */
  14. @ServerEndpoint("/ws/serverOne")
  15. @Component
  16. public class WebSocketServerOne {
  17. //concurrent包下线程安全的Set
  18. private static final CopyOnWriteArraySet SESSIONS = new CopyOnWriteArraySet<>();
  19. private Session session;
  20. @OnOpen
  21. public void onOpen(Session session) {
  22. this.session = session;
  23. SESSIONS.add(this);
  24. System.out.println(String.format("成功建立连接~ 当前总连接数为:%s", SESSIONS.size()));
  25. System.out.println(this);
  26. }
  27. @OnClose
  28. public void onClose() {
  29. SESSIONS.remove(this);
  30. System.out.println(String.format("成功关闭连接~ 当前总连接数为:%s", SESSIONS.size()));
  31. }
  32. @OnMessage
  33. public void onMessage(String message, Session session) {
  34. System.out.println(message);
  35. }
  36. @OnError
  37. public void onError(Session session, Throwable error) {
  38. System.out.println("发生错误");
  39. error.printStackTrace();
  40. }
  41. /**
  42. * 指定发消息
  43. *
  44. * @param message
  45. */
  46. public void sendMessage(String message) {
  47. try {
  48. this.session.getBasicRemote().sendText(message);
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. /**
  54. * 群发消息
  55. *
  56. * @param message
  57. */
  58. public static void fanoutMessage(String message) {
  59. SESSIONS.forEach(ws -> ws.sendMessage(message));
  60. }
  61. }

这时,一个传统的 WebSocket 应用就开发完毕了。由于我是在 SpringBoot 下开发的,所以有一些调整。

启动应用后搞个普通的 html 页面直接本地打开就可以试验是否能够连接上了。

  1. "en">
  2. "UTF-8">
  3. webSocket
  4. WebSocket Demo.

  5. WebSocket Demo..

  6. WebSocket Demo...

  7. type="button" onclick="websocket.send('666666666')" value="点我发消息"/>

不过… 我既然都导入了 spring-boot-starter-websocket 依赖,自然最好还是用 SprinBoot 推荐的方法比较好。

这是使用 SpringBoot 的形式构建 WebSocket 应用程序:

这是核心配置类。实现 WebSocketConfigurer 接口实现它提供的注册方法。

这个方法就厉害了, 可以配置 websocket 入口,允许访问的域、注册 Handler、定义拦截器等等等等。

@EnableWebSocket 用于开启注解接收和发送消息

  1. package com.skypyb.springboot_websocket;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.http.server.ServerHttpRequest;
  4. import org.springframework.http.server.ServerHttpResponse;
  5. import org.springframework.http.server.ServletServerHttpRequest;
  6. import org.springframework.web.socket.WebSocketHandler;
  7. import org.springframework.web.socket.config.annotation.EnableWebSocket;
  8. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
  9. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
  10. import org.springframework.web.socket.server.HandshakeInterceptor;
  11. import java.util.Map;
  12. @Configuration
  13. @EnableWebSocket
  14. public class WebSocketConfigTwo implements WebSocketConfigurer {
  15. @Override
  16. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  17. registry.addHandler(new MyWebSocketHandler(), "/ws/serverTwo")//设置连接路径和处理
  18. .setAllowedOrigins("*")
  19. .addInterceptors(new MyWebSocketInterceptor());//设置拦截器
  20. }
  21. /**
  22. * 自定义拦截器拦截WebSocket请求
  23. */
  24. class MyWebSocketInterceptor implements HandshakeInterceptor {
  25. //前置拦截一般用来注册用户信息,绑定 WebSocketSession
  26. @Override
  27. public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
  28. WebSocketHandler wsHandler, Map attributes) throws Exception {
  29. System.out.println("前置拦截~~");
  30. if (!(request instanceof ServletServerHttpRequest)) return true;
  31. // HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
  32. // String userName = (String) servletRequest.getSession().getAttribute("userName");
  33. String userName = "Koishipyb";
  34. attributes.put("userName", userName);
  35. return true;
  36. }
  37. @Override
  38. public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
  39. WebSocketHandler wsHandler, Exception exception) {
  40. System.out.println("后置拦截~~");
  41. }
  42. }
  43. }

关于他这个拦截器, 是非常重要的,最好还是设置一个。他可以在连接进入到 Handler 处理时进行一些操作。

比如从 session 中拿出用户登陆信息作为唯一标识符等等…

我把我的拦截器实现写成内部类了,反正也没多少东西。这个配置注册的处理器和拦截器,都是只有一个的,无论你多少连接进来,都是用相同的对象处理。

那么就不好用传统的 WebSocket 开发那样用个集合类容器来存了。用前置拦截设置进去的某些唯一标识作为 key,session 作为 value 用键值对映射类容器来存储连接是比较好的方案。

推荐阅读:Github上最值得学习的100个Java开源项目,涵盖各种技术栈!

这是我实现的处理器, 也是 WebSocket 开发的核心:

需要实现 WebSocketHandler 接口, 该接口提供了五个方法。

  • 1、afterConnectionEstablished(): 建立新的 socket 连接后回调的方法。

  • 2、handleMessage(): 接收客户端发送的 Socket。

  • 3、handleTransportError(): 连接出错时,回调的方法。

  • 4、afterConnectionClosed(): 连接关闭时,回调的方法。

  • 5、supportsPartialMessages(): 这个是 WebSocketHandler 是否处理部分消息,没什么卵用 返回 false 就完事了。

  1. package com.skypyb.springboot_websocket;
  2. import org.springframework.web.socket.*;
  3. import java.io.IOException;
  4. import java.util.Map;
  5. import java.util.concurrent.ConcurrentHashMap;
  6. public class MyWebSocketHandler implements WebSocketHandler {
  7. private static final Map SESSIONS = new ConcurrentHashMap<>();
  8. @Override
  9. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  10. String userName = session.getAttributes().get("userName").toString();
  11. SESSIONS.put(userName, session);
  12. System.out.println(String.format("成功建立连接~ userName: %s", userName));
  13. }
  14. @Override
  15. public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception {
  16. String msg = message.getPayload().toString();
  17. System.out.println(msg);
  18. }
  19. @Override
  20. public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  21. System.out.println("连接出错");
  22. if (session.isOpen()) {
  23. session.close();
  24. }
  25. }
  26. @Override
  27. public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
  28. System.out.println("连接已关闭,status:" + closeStatus);
  29. }
  30. @Override
  31. public boolean supportsPartialMessages() {
  32. return false;
  33. }
  34. /**
  35. * 指定发消息
  36. *
  37. * @param message
  38. */
  39. public static void sendMessage(String userName, String message) {
  40. WebSocketSession webSocketSession = SESSIONS.get(userName);
  41. if (webSocketSession == null || !webSocketSession.isOpen()) return;
  42. try {
  43. webSocketSession.sendMessage(new TextMessage(message));
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. /**
  49. * 群发消息
  50. *
  51. * @param message
  52. */
  53. public static void fanoutMessage(String message) {
  54. SESSIONS.keySet().forEach(us -> sendMessage(us, message));
  55. }
  56. }

至此,SpringBoot 与 WebSocket 已经集成完了。客户端仍然使用我上边那个 HTML 文件就行,访问地址改一下就完事了。

我的这个 WebSocket 项目整体源码地址https://github.com/skypyb/codeDemo/tree/master/WebSocket


(完)

  1. MarkerHub文章索引:(点击阅读原文直达)
  2. https://github.com/MarkerHub/JavaIndex
  3. 【推荐阅读】
  4. 小白教程,Springboot项目搭建(前端到数据库,超详细)
  5. Spring 和 Spring Boot 最核心的 3 大区别,详解!
  6. 就几条命令,一键学会Docker部署SpringBoot项目
  7. 使用 SpringBoot2.X 实现 Quartz 动态任务的分布式调度
  8. 从集成到ACK、消息重试、死信队列,Kafka你知多少?

好文!必须点赞

文章知识点与官方知识档案匹配,可进一步学习相关知识
网络技能树首页概览45701 人正在系统学习中
注:本文转载自blog.csdn.net的Java思维导图的文章"https://blog.csdn.net/java_mindmap/article/details/105898152"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top