WebSocket項目筆記 1. What is WebSocket? (以下內容來源於百度百科) WebSocket是一種在單個TCP連接上進行全雙工通信的協議 WebSocket使得客戶端和伺服器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。 在WebSocket API中,瀏覽器 ...
WebSocket項目筆記
1. What is WebSocket?
(以下內容來源於百度百科)
- WebSocket是一種在單個TCP連接上進行全雙工通信的協議
- WebSocket使得客戶端和伺服器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。
- 在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,併進行雙向數據傳輸。
- 背景:
- 推送技術的演進發展:輪詢 ---> Comet ---> WebSocket
-
握手協議:
- WebSocket 是獨立的、創建在 TCP 上的協議。
- Websocket 通過HTTP/1.1 協議的101狀態碼進行握手。
- 為了創建Websocket連接,需要通過瀏覽器發出請求,之後伺服器進行回應,這個過程通常稱為“握手”(handshaking)。
2. Let's try!
因為項目要求,我的小程式需要與後端伺服器進行長連接,以傳送數據。所以我需要在我配置好的Springboot框架中添加WebSocket。十分慶幸的是,Springboot已經集成好了WebSocket了。所以過程並不複雜。看了很多博客,內容都大同小異。
我有點懵,因為我發現大家都在說的是,客戶端與伺服器建立連接的過程的實現。所以有幾個問題:
- 既然是伺服器主動發送消息,那麼伺服器到底 “到底什麼時候發送消息呢?怎麼發送?”
- 如何傳參數,傳的參數如何接收與使用。
- 我只需要針對某個客戶端發消息,在同時有多個socket連接的時候我怎麼標識該客戶端?
大概就有這些。下麵我們在配置過程中將問題逐個擊破!
- 開發環境:Springboot 1.5.19 Java1.8
- 配置pom文件
<!-- 引入 websocket 依賴類--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
- 配置Springboot開啟WebSocket支持
package com.cuc.happyseat.config.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * 開啟WebSocket支持 */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
- WebSocket服務類編寫
這裡對照下麵的代碼看:
- 第20行,@ServerEndpoint("/websocket/{userID}"),括弧中的內容就是客戶端請求Socket連接時的訪問路徑,userID是我要求客戶端傳來的參數,我這裡算是為了標識該客戶端吧。
- 第28行,在該類中添加屬性 userID,並添加對應的getUserID()方法。
- 第46行,在onOpen()方法即建立連接的時候就接收參數userID,需要標識@PathParam("userID") 。接收參數後直接賦值給屬性userID。
- 第140-157行,是針對特定客戶端發送消息。伺服器和客戶端在建立連接成功後就生成了一個WebSocket對象,並存在集合中,對象里特有的屬性是我們設置的userID。所以通過唯一的userID就能標識伺服器與該客戶端建立的那個連接啦!這樣要求發送消息時,傳入userID與消息,伺服器在自己的WebSocket連接集合中遍歷找到對應客戶端的連接,就可以直接發消息過去啦~~
1 package com.cuc.happyseat.websocket; 2 3 import java.io.IOException; 4 import java.util.concurrent.CopyOnWriteArraySet; 5 6 import javax.websocket.EncodeException; 7 import javax.websocket.OnClose; 8 import javax.websocket.OnError; 9 import javax.websocket.OnMessage; 10 import javax.websocket.OnOpen; 11 import javax.websocket.Session; 12 import javax.websocket.server.PathParam; 13 import javax.websocket.server.ServerEndpoint; 14 15 import org.springframework.stereotype.Component; 16 17 /*@ServerEndpoint註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket伺服器端, 18 * 註解的值將被用於監聽用戶連接的終端訪問URL地址,客戶端可以通過這個URL來連接到WebSocket伺服器端 19 */ 20 @ServerEndpoint("/websocket/{userID}") 21 @Component 22 public class WebSocketServer { 23 24 //每個客戶端都會有相應的session,服務端可以發送相關消息 25 private Session session; 26 27 //接收userID 28 private Integer userID; 29 30 //J.U.C包下線程安全的類,主要用來存放每個客戶端對應的webSocket連接 31 private static CopyOnWriteArraySet<WebSocketServer> copyOnWriteArraySet = new CopyOnWriteArraySet<WebSocketServer>(); 32 33 public Integer getUserID() { 34 return userID; 35 } 36 37 /** 38 * @Name:onOpen 39 * @Description:打開連接。進入頁面後會自動發請求到此進行連接 40 * @Author:mYunYu 41 * @Create Date:14:46 2018/11/15 42 * @Parameters:@PathParam("userID") Integer userID 43 * @Return: 44 */ 45 @OnOpen 46 public void onOpen(Session session, @PathParam("userID") Integer userID) { 47 this.session = session; 48 this.userID = userID; 49 System.out.println(this.session.getId()); 50 //System.out.println("userID:" + userID); 51 copyOnWriteArraySet.add(this); 52 System.out.println("websocket有新的連接, 總數:"+ copyOnWriteArraySet.size()); 53 54 } 55 56 /** 57 * @Name:onClose 58 * @Description:用戶關閉頁面,即關閉連接 59 * @Author:mYunYu 60 * @Create Date:14:46 2018/11/15 61 * @Parameters: 62 * @Return: 63 */ 64 @OnClose 65 public void onClose() { 66 copyOnWriteArraySet.remove(this); 67 System.out.println("websocket連接斷開, 總數:"+ copyOnWriteArraySet.size()); 68 } 69 70 /** 71 * @Name:onMessage 72 * @Description:測試客戶端發送消息,測試是否聯通 73 * @Author:mYunYu 74 * @Create Date:14:46 2018/11/15 75 * @Parameters: 76 * @Return: 77 */ 78 @OnMessage 79 public void onMessage(String message) { 80 System.out.println("websocket收到客戶端發來的消息:"+message); 81 } 82 83 /** 84 * @Name:onError 85 * @Description:出現錯誤 86 * @Author:mYunYu 87 * @Create Date:14:46 2018/11/15 88 * @Parameters: 89 * @Return: 90 */ 91 @OnError 92 public void onError(Session session, Throwable error) { 93 System.out.println("發生錯誤:" + error.getMessage() + "; sessionId:" + session.getId()); 94 error.printStackTrace(); 95 } 96 97 public void sendMessage(Object object){ 98 //遍歷客戶端 99 for (WebSocketServer webSocket : copyOnWriteArraySet) { 100 System.out.println("websocket廣播消息:" + object.toString()); 101 try { 102 //伺服器主動推送 103 webSocket.session.getBasicRemote().sendObject(object) ; 104 } catch (Exception e) { 105 e.printStackTrace(); 106 } 107 } 108 } 109 110 /** 111 * @Name:sendMessage 112 * @Description:用於發送給客戶端消息(群發) 113 * @Author:mYunYu 114 * @Create Date:14:46 2018/11/15 115 * @Parameters: 116 * @Return: 117 */ 118 public void sendMessage(String message) { 119 //遍歷客戶端 120 for (WebSocketServer webSocket : copyOnWriteArraySet) { 121 System.out.println("websocket廣播消息:" + message); 122 try { 123 //伺服器主動推送 124 webSocket.session.getBasicRemote().sendText(message); 125 } catch (Exception e) { 126 e.printStackTrace(); 127 } 128 } 129 } 130 131 /** 132 * @throws Exception 133 * @Name:sendMessage 134 * @Description:用於發送給指定客戶端消息 135 * @Author:mYunYu 136 * @Create Date:14:47 2018/11/15 137 * @Parameters: 138 * @Return: 139 */ 140 public void sendMessage(Integer userID, String message) throws Exception { 141 Session session = null; 142 WebSocketServer tempWebSocket = null; 143 for (WebSocketServer webSocket : copyOnWriteArraySet) { 144 if (webSocket.getUserID() == userID) { 145 tempWebSocket = webSocket; 146 session = webSocket.session; 147 break; 148 } 149 } 150 if (session != null) { 151 //伺服器主動推送 152 tempWebSocket.session.getBasicRemote().sendText(message); 153 154 } else { 155 System.out.println("沒有找到你指定ID的會話:{}"+ "; userId:" + userID); 156 } 157 } 158 159 160 161 }
- Controller類的編寫。
- 我在看博客的時候,發現有的博主寫了Controller類,有的沒寫,我就有點疑惑了。後來,咳咳,發現特地寫了一個Controller類只是為了測試。。
- 一般在實際項目中,在確保建立連接過程沒有問題的情況下,我們就直接在一些寫好的介面中寫 WebSocketServer.sendMessage(param, message)語句就行了。
- 也因此,你寫的位置就決定了你什麼時候給你的客戶端發消息,這樣也就實現了主動推送消息的功能咯~
- 前提是:在你的Controller類里,以@Resource的方式註入WebSocket,而不是@Autowired方式哦(⊙o⊙)。
@Resource
WebSocketServer webSocket;
@Autowired
UserService userService;
調用示例:
if(userID>0) { boolean location = userService.getLocation(userID); if(location==false) {//驗證用戶當前不在館內 boolean i = userService.modifyLocation(userID, true); if(i==true) { modelMap.put("successEnter", true); //發消息給客戶端 webSocket.sendMessage(userID, "success"); } }else { modelMap.put("successEnter", false); //發消息給客戶端 webSocket.sendMessage(userID, "fail"); } }else { modelMap.put("successEnter", false); //發消息給客戶端 webSocket.sendMessage(userID, "fail"); }
- 前端測試
因為我只寫後端,前端部分小姐姐說看微信的官方文檔就可以啦~ 鏈接:在此!微信封裝好了吧,好像不難。
3. Problems
- 前期我看很多博客,的確產生很多問題,想不通,主要就是上面幾個問題。然後我寫了讓前端先連WebSocket試了一下,想自己瞭解一下連接過程(也就是在控制台輸出的文件中查),後來就慢慢通了。可以說是一帆風順了??
- 不過我確保我沒問題不算,得前端說了算對吧。所以,,我背了鍋