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+'\n');
};
ws.onclose = function(){
$('#message_content').append('用戶['+username+'] 已經離開聊天室!' + '\n');
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的簡單實現,更多情況...
