SpringBoot之集成WebSocket
Posted 边见众生,边见自己
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot之集成WebSocket相关的知识,希望对你有一定的参考价值。
websocket是什么不做介绍。开发环境:jdk1.8,win7_64旗舰版,idea
1、初始化一个springboot项目
2、加入websocket依赖
<!-- springboot的websocket依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
pom.xml如下:
<dependencies> <!-- 模板引擎 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- websocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!----> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- lombok工具 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 内置tomcat --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <!-- 测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
3、编写websocket的服务端
3.1、WebSocketEndPoint是websocket服务端的核心
@PathParam是javax.websocket.server下的注解,是将路径中绑定的占位符的值取出来
在url中使用key和name,是想通过key和name对websocket的连接进行访问控制,这个key可以是用户登录后服务器给用户的令牌,通过令牌和和name进行权限验证(自己写拦截器或者继承权限框架实现),还可以通过key和name生成唯一值来进行在线websocket
连接的维护<(key+name), websocketSession>, 当然,我在这里没有这样做。
package com.geniuses.sewage_zero_straight.net.websocket; import com.geniuses.sewage_zero_straight.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.Map; import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketPool.*; import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketHandler.createKey; @Slf4j @Component @ServerEndpoint("/net/websocket/{key}/{name}")//表明这是一个websocket服务的端点 public class WebSocketEndPoint { private static UserService userService; @Autowired public void setUserService(UserService userService){ WebSocketEndPoint.userService = userService; } @OnOpen public void onOpen(@PathParam("key") String key, @PathParam("name") String name, Session session){ log.info("有新的连接:{}", session); add(createKey(key, name), session); WebSocketHandler.sendMessage(session, key + name); log.info("在线人数:{}",count()); sessionMap().keySet().forEach(item -> log.info("在线用户:", item)); for (Map.Entry<String, Session> item : sessionMap().entrySet()){ log.info("12: {}", item.getKey()); } } @OnMessage public void onMessage(String message){ log.info("有新消息: {}", message); } @OnClose public void onClose(@PathParam("key") String key, @PathParam("name") String name,Session session){ log.info("连接关闭: {}", session); remove(createKey(key, name)); log.info("在线人数:{}",count()); sessionMap().keySet().forEach(item -> log.info("在线用户:", (item.split("@"))[1])); for (Map.Entry<String, Session> item : sessionMap().entrySet()){ log.info("12: {}", item.getKey()); } } @OnError public void onError(Session session, Throwable throwable){ try { session.close(); } catch (IOException e) { log.error("onError Exception: {}", e); } log.info("连接出现异常: {}", throwable); } }
3.2、WebSocketPool是websocket的在线连接池
package com.geniuses.sewage_zero_straight.net.websocket; import lombok.extern.slf4j.Slf4j; import javax.websocket.Session; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Slf4j public class WebSocketPool { //在线用户websocket连接池 private static final Map<String, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>(); /** * 新增一则连接 * @param key * @param session */ public static void add(String key, Session session){ if (!key.isEmpty() && session != null){ ONLINE_USER_SESSIONS.put(key, session); } } /** * 根据Key删除连接 * @param key */ public static void remove(String key){ if (!key.isEmpty()){ ONLINE_USER_SESSIONS.remove(key); } } /** * 获取在线人数 * @return */ public static int count(){ return ONLINE_USER_SESSIONS.size(); } /** * 获取在线session池 * @return */ public static Map<String, Session> sessionMap(){ return ONLINE_USER_SESSIONS; } }
3.3、WebSocketHandler是websocket的动作处理工具
package com.geniuses.sewage_zero_straight.net.websocket; import lombok.extern.slf4j.Slf4j; import javax.websocket.RemoteEndpoint; import javax.websocket.Session; import java.io.IOException; import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketPool.sessionMap; @Slf4j public class WebSocketHandler { /** * 根据key和用户名生成一个key值,简单实现下 * @param key * @param name * @return */ public static String createKey(String key, String name){ return key + "@" + name; } /** * 给指定用户发送信息 * @param session * @param msg */ public static void sendMessage(Session session, String msg){ if (session == null) return; final RemoteEndpoint.Basic basic = session.getBasicRemote(); if (basic == null) return; try { basic.sendText(msg); } catch (IOException e) { log.error("sendText Exception: {}", e); } } /** * 给所有的在线用户发送消息 * @param message */ public static void sendMessageAll(String message){ log.info("广播:群发消息"); sessionMap().forEach((key, session) -> sendMessage(session, message)); } }
4、前端访问实现
4.1、index.html,页面引用了jquery和bootstrap样式,请自行应用
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.springframework.org/schema/mvc"> <head> <meta charset="UTF-8"> <title>chat room websocket</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> <script th:src="@{/js/jquery-3.3.1.min.js}" ></script> </head> <body class="container" style="width: 60%"> <div class="form-group" ></br> <h5>聊天室</h5> <textarea id="message_content" class="form-control" readonly="readonly" cols="50" rows="10"></textarea> </div> <div class="form-group" > <label for="in_user_name">用户姓名 </label> <input id="in_user_name" value="" class="form-control" /></br> <button id="user_join" class="btn btn-success" >加入聊天室</button> <button id="user_exit" class="btn btn-warning" >离开聊天室</button> </div> <div class="form-group" > <label for="in_room_msg" >群发消息 </label> <input id="in_room_msg" value="" class="form-control" /></br> <button id="user_send_all" class="btn btn-info" >发送消息</button> </div> </body> <<script type="text/javascript"> $(document).ready(function(){ var urlPrefix =‘ws://192.168.2.156:8080/net/websocket/12/‘; var ws = null; $(‘#user_join‘).click(function(){ var username = $(‘#in_user_name‘).val(); var url = urlPrefix + username; ws = new WebSocket(url); ws.onopen = function () { console.log("建立 websocket 连接..."); }; ws.onmessage = function(event){ //服务端发送的消息 $(‘#message_content‘).append(event.data+‘ ‘); }; ws.onclose = function(){ $(‘#message_content‘).append(‘用户[‘+username+‘] 已经离开聊天室!‘ + ‘ ‘); console.log("关闭 websocket 连接..."); } }); //客户端发送消息到服务器 $(‘#user_send_all‘).click(function(){ var msg = $(‘#in_room_msg‘).val(); if(ws){ ws.send(msg); } }); // 退出聊天室 $(‘#user_exit‘).click(function(){ if(ws){ ws.close(); } }); }) </script> </html>
4.2、页面访问控制器,由此来访问index.html
package com.geniuses.sewage_zero_straight.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @RequestMapping("/view") @Controller public class ViewController { /** * 返回首页 * @return */ @GetMapping("/index") public String index(){ return "index"; } }
5、websocket配置
package com.geniuses.sewage_zero_straight.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration @EnableWebSocket public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } }
6、注意:
在使用了@ServerEndpoint注解的类是无法直接使用@Autowired的,因为@ServerEndpoint表明当前类是websocket的服务端点,在spring容器启动时会初始化一次该类,当有新的websocket连接的时候,也会进行该类实例的创建(每一次连接时都会创建一个实例),所以在第二次往后创建该类实例的时候,就无法进行有效的@Autowired了,此时发现,即便第一次注入是有效的,但是也没有什么用。这个时候,将需要注入的变量置为类的变量,提供一个set方法(该方法为实例方法),在set方法上面进行依赖注入,这样就可以进行有效的注入了。
7、这里只是websocket的简单实现,更多情况...
以上是关于SpringBoot之集成WebSocket的主要内容,如果未能解决你的问题,请参考以下文章
项目总结48:Springboot集成Websocket案例