一、什么是WebSocket?
B/S結構的軟件項目中有時客戶端需要實時的獲得服務器消息,但默認HTTP協議只支持請求響應模式,這樣做可以簡化Web服務器,減少服務器的負擔,加快響應速度,因為服務器不需要與客戶端長時間建立一個通信鏈接,但不容易直接完成實時的消息推送功能,如聊天室、后台信息提示、實時更新數據等功能,但通過polling、Long polling、長連接、Flash Socket以及HTML5中定義的WebSocket能完成該功能需要。
WebSocket是HTML5開始提供的一種在單個TCP連接上進行全雙工通訊的協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據,在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。
Socket又稱"套接字",應用程序通常通過"套接字"向網絡發出請求或者應答網絡請求。Socket的英文原義是“孔”或“插座”,作為UNIX的進程通信機制。Socket可以實現應用程序間網絡通信。
Socket可以使用TCP/IP協議或UDP協議。
TCP/IP協議
TCP/IP協議是目前應用最為廣泛的協議,是構成Internet國際互聯網協議的最為基礎的協議,由TCP和IP協議組成:
TCP協議:面向連接的、可靠的、基於字節流的傳輸層通信協議,負責數據的可靠性傳輸的問題。
IP協議:用於報文交換網絡的一種面向數據的協議,主要負責給每台網絡設備一個網絡地址,保證數據傳輸到正確的目的地。
UDP協議
UDP特點:無連接、不可靠、基於報文的傳輸層協議,優點是發送后不用管,速度比TCP快。
二、SpringBoot整合WebSocket
新建一個spring boot項目spring-boot-websocket,按照下面步驟操作。
1.pom.xml引入jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
2.新建WebSocket的配置類
這個配置類檢測帶注解@ServerEndpoint的bean並注冊它們,配置類代碼如下:
@Configuration public class WebSocketConfig { /** * 給spring容器注入這個ServerEndpointExporter對象 * 相當於xml: * <beans> * <bean id="serverEndpointExporter" class="org.springframework.web.socket.server.standard.ServerEndpointExporter"/> * </beans> * <p> * 檢測所有帶有@serverEndpoint注解的bean並注冊他們。 * * @return */ @Bean public ServerEndpointExporter serverEndpointExporter() { System.out.println("我被注入了"); return new ServerEndpointExporter(); } }
3.新建WebSocket的處理類
這個處理類需要使用@ServerEndpoint,這個類里監聽連接的建立關閉、消息的接收等,具體代碼如下:
@ServerEndpoint(value = "/ws/asset") @Component public class WebSocketServer { @PostConstruct public void init() { System.out.println("websocket 加載"); } private static Logger log = LoggerFactory.getLogger(WebSocketServer.class); private static final AtomicInteger OnlineCount = new AtomicInteger(0); // concurrent包的線程安全Set,用來存放每個客戶端對應的Session對象。 private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>(); /** * 連接建立成功調用的方法 */ @OnOpen public void onOpen(Session session) { SessionSet.add(session); int cnt = OnlineCount.incrementAndGet(); // 在線數加1 log.info("有連接加入,當前連接數為:{}", cnt); SendMessage(session, "連接成功"); } /** * 連接關閉調用的方法 */ @OnClose public void onClose(Session session) { SessionSet.remove(session); int cnt = OnlineCount.decrementAndGet(); log.info("有連接關閉,當前連接數為:{}", cnt); } /** * 收到客戶端消息后調用的方法 * * @param message * 客戶端發送過來的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("來自客戶端的消息:{}",message); SendMessage(session, "收到消息,消息內容:"+message); } /** * 出現錯誤 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("發生錯誤:{},Session ID: {}",error.getMessage(),session.getId()); error.printStackTrace(); } /** * 發送消息,實踐表明,每次瀏覽器刷新,session會發生變化。 * @param session * @param message */ public static void SendMessage(Session session, String message) { try { // session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId())); session.getBasicRemote().sendText(message); } catch (IOException e) { log.error("發送消息出錯:{}", e.getMessage()); e.printStackTrace(); } } /** * 群發消息 * @param message * @throws IOException */ public static void BroadCastInfo(String message) throws IOException { for (Session session : SessionSet) { if(session.isOpen()){ SendMessage(session, message); } } } /** * 指定Session發送消息 * @param sessionId * @param message * @throws IOException */ public static void SendMessage(String message,String sessionId) throws IOException { Session session = null; for (Session s : SessionSet) { if(s.getId().equals(sessionId)){ session = s; break; } } if(session!=null){ SendMessage(session, message); } else{ log.warn("沒有找到你指定ID的會話:{}",sessionId); } } }
4.Html編寫方式
目前大部分瀏覽器支持WebSocket,比如Chrome, Mozilla,Opera和Safari,在html頁面進行websocket的連接建立、收消息的監聽,頁面代碼如下:
<html> <head> <meta charset="UTF-8"> <title>websocket測試</title> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> <style type="text/css"> h3,h4{ text-align:center; } </style> </head> <body> <h3>WebSocket測試,客戶端接收到的消息如下:</h3> <textarea id = "messageId" readonly="readonly" cols="150" rows="30" > </textarea> <script type="text/javascript"> var socket; if (typeof (WebSocket) == "undefined") { console.log("遺憾:您的瀏覽器不支持WebSocket"); } else { console.log("恭喜:您的瀏覽器支持WebSocket"); //實現化WebSocket對象 //指定要連接的服務器地址與端口建立連接 //注意ws、wss使用不同的端口。我使用自簽名的證書測試, //無法使用wss,瀏覽器打開WebSocket時報錯 //ws對應http、wss對應https。 socket = new WebSocket("ws://localhost:8080/ws/asset"); //連接打開事件 socket.onopen = function() { console.log("Socket 已打開"); socket.send("消息發送測試(From Client)"); }; //收到消息事件 socket.onmessage = function(msg) { $("#messageId").append(msg.data+ "\n"); console.log(msg.data ); }; //連接關閉事件 socket.onclose = function() { console.log("Socket已關閉"); }; //發生了錯誤事件 socket.onerror = function() { alert("Socket發生了錯誤"); } //窗口關閉時,關閉連接 window.unload=function() { socket.close(); }; } </script> </body> </html>
三、業務調用
WebSocket ws = new WebSocket(); JSONObject jo = new JSONObject(); jo.put("message", "這個比密碼不對還想登錄!"); jo.put("To", "admin");// 給用戶名為admin的用戶推送 try { ws.onMessage(jo.toString()); } catch (IOException e) { e.printStackTrace(); }
四、 查看運行效果
啟動SpringBoot項目
1.打開首頁
本地瀏覽器打開首頁http://localhost:8080/,出現WebSocket測試頁面,同時后台打印連接的日志。
有連接加入,當前連接數為:1,sessionId=0
2.往客戶端發送消息
通過上面日志可以看到客戶端連接連接的sessionId,我測試時候sessionId是0,然后瀏覽器訪問下面接口即可往客戶端發送消息。
//參數說明: id:sessionID //參數說明: message:消息內容 http://localhost:8080/api/ws/sendOne?id=0&message=你好Java碎碎念
到此SpringBoot整合WebSocket的功能已經全部實現。
完整源碼地址: https://github.com/suisui2019/springboot-study