WebSocket簡介與消息推送
B/S架構的系統多使用HTTP協議,HTTP協議的特點:
1 無狀態協議
2 用於通過 Internet 發送請求消息和響應消息
3 使用端口接收和發送消息,默認為80端口
底層通信還是使用Socket完成。

HTTP協議決定了服務器與客戶端之間的連接方式,無法直接實現消息推送(F5已壞),一些變相的解決辦法:
雙向通信與消息推送
輪詢:客戶端定時向服務器發送Ajax請求,服務器接到請求后馬上返回響應信息並關閉連接。 優點:后端程序編寫比較容易。 缺點:請求中有大半是無用,浪費帶寬和服務器資源。 實例:適於小型應用。
長輪詢:客戶端向服務器發送Ajax請求,服務器接到請求后hold住連接,直到有新消息才返回響應信息並關閉連接,客戶端處理完響應信息后再向服務器發送新的請求。 優點:在無消息的情況下不會頻繁的請求,耗費資小。 缺點:服務器hold連接會消耗資源,返回數據順序無保證,難於管理維護。 Comet異步的ashx,實例:WebQQ、Hi網頁版、Facebook IM。
長連接:在頁面里嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設為對一個長連接的請求或是采用xhr請求,服務器端就能源源不斷地往客戶端輸入數據。 優點:消息即時到達,不發無用請求;管理起來也相對便。 缺點:服務器維護一個長連接會增加開銷。 實例:Gmail聊天
Flash Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程序JavaScript通過調用此Flash程序提供的Socket接口與服務器端的Socket接口進行通信,JavaScript在收到服務器端傳送的信息后控制頁面的顯示。 優點:實現真正的即時通信,而不是偽即時。 缺點:客戶端必須安裝Flash插件;非HTTP協議,無法自動穿越防火牆。 實例:網絡互動游戲。
Websocket:
WebSocket是HTML5開始提供的一種瀏覽器與服務器間進行全雙工通訊的網絡技術。依靠這種技術可以實現客戶端和服務器端的長連接,雙向實時通信。
特點:
事件驅動
異步
使用ws或者wss協議的客戶端socket
能夠實現真正意義上的推送功能
缺點:
少部分瀏覽器不支持,瀏覽器支持的程度與方式有區別。

JavaEE 7中出了JSR-356:Java API for WebSocket規范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。
一、下面demo使用jetty實現:
項目結構:

java后台代碼:
package edu.nf.ws.server; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.Set; /** * @author wangl * @date 2018-12-05 * websocket服務端 */ @ServerEndpoint("/chat/server/{userName}") public class ChatServer { /** * 當有客戶端連接到服務端的時候就會調用這個方法 * session代表客戶端和服務端的一個連接會話對象 * ,由容器負責創建和維護 */ @OnOpen public void onOpen(Session session, @PathParam("userName") String userName){ System.out.println("有客戶端連接..."+userName); //將用戶名保存到當前用戶會話的屬性中(有點類似作用域的概念) session.getUserProperties().put("user", userName); } /** * 客戶端和服務器之間通信的方法, * 服務端每當接收到客戶端的消息就會調用這個方法 * ,注意:必須指定一個String類型的參數,表示接收到客戶端的文本消息 */ @OnMessage public void onMessage(String message, Session session) throws IOException{ System.out.println("接收消息..." + message); //將消息發送給所有人 sendAllUser(message, session); } /** * 當客戶端關閉或者斷開連接時,服務端會調用此方法 * @param session */ @OnClose public void opnClose(Session session) throws IOException{ System.out.println("客戶端失去連接..."); //關閉會話 session.close(); } private void sendAllUser(String message, Session session) throws IOException{ //獲取所有人的會話對象 Set<Session> users = session.getOpenSessions(); //獲取發送人 String sendUser = session.getUserProperties().get("user").toString(); //發送給所有人 for (Session user : users) { user.getBasicRemote().sendText(sendUser + " : " + message); } } }
html代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="loginDiv"> 用戶名:<input type="text" id="userName" name="userName"/> <input type="button" id="login" value="login"/> </div> <div id="container" style="display: none"> <div id="content"></div> <input type="text" name="msg" id="msg"/> <input type="button" id="send" value="send"/> </div> <script src="js/jquery-3.3.1.min.js"></script> <script> $(function(){ var ws; //登陸 $('#login').on('click',function(){ var userName = $('#userName').val(); //創建websocket對象並連接服務端 ws = new WebSocket('ws://localhost:8080/chat/server/' + userName); //客戶端打開連接時會回調此方法 /*ws.onopen = function(){ //... }*/ //客戶端關閉或斷開連接時執行此方法 /*ws.onclose = function(){ //... }*/ //接收服務端發送的消息 ws.onmessage = function(message){ $('#content').append(message.data + "<br>"); } $('#loginDiv').css('display','none'); $('#container').css('display','block'); }); //發送消息 $('#send').on('click',function(){ var msg = $('#msg').val(); //發送消息 ws.send(msg); }); }) </script> </body> </html>

二、spring+jetty實現
項目結構:

pom 配置:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>edu.nf</groupId> <artifactId>spring-ws</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> <!-- spring版本 --> <spring.version>5.1.1.RELEASE</spring.version> <servlet.version>4.0.1</servlet.version> <jackson.version>2.9.7</jackson.version> </properties> <!-- 添加依賴 --> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> </dependencies> <!-- war插件 --> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.2</version> <configuration> <warSourceDirectory>web</warSourceDirectory> <!-- 指定web.xml路徑 --> <webXml>web\WEB-INF\web.xml</webXml> </configuration> </plugin> </plugins> </build> </project>
web.xml配置:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:dispatcher-servlet.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
ServerEndpointHandler(服務端)
package edu.nf.demo.websocket; import edu.nf.demo.entity.Users; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author wangl * @date 2018-12-06 * websocket服務端 */ public class ServerEndpointHandler extends TextWebSocketHandler { /** * 維護一個用戶列表(key存放用戶名,value存放每一個用戶的WebSocketSession) */ private static Map<String, WebSocketSession> users = new ConcurrentHashMap<>(); /** * 客戶端建立連接之后執行此方法(onOpen) * @param session 每當客戶端連接后,容器會為其創建一個Session對象, * 這個對象在Spring中就是WebSocketSession * @throws Exception */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { System.out.println("客戶端建立了連接..."); //獲取用戶名,getAttributes方法得到的是一個Map, //這個map里面存放了握手攔截器將HttpSession作用域拷貝過去的數據 Users user = (Users)session.getAttributes().get("user"); //將用戶的session保存到用戶列表中 users.put(user.getUserName(), session); } /** * 每當客戶端發送消息時執行此方法(onmessage) * @param session * @param message TextMessage對象表示接收客戶端的文本消息對象, * 它的getPayload方法將獲取具體消息內容 * @throws Exception */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { System.out.println("接收客戶端消息..." + message.getPayload()); //獲取用戶名 Users sendUser = (Users)session.getAttributes().get("user"); //群發消息 for(String userName : users.keySet()){ //重新構建一個TextMessage對象 TextMessage newMessage = new TextMessage(sendUser.getUserName() + " : " + message.getPayload()); //發送所有人 users.get(userName).sendMessage(newMessage); } } /** * 哭護短關閉或斷開連接時執行此方法(onclose) * @param session * @param status * @throws Exception */ @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { System.out.println("客戶端斷開連接..."); session.close(); } }
UserController(請求控制類)
package edu.nf.demo.controller; import edu.nf.demo.controller.vo.ResponseVO; import edu.nf.demo.entity.Users; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; /** * @author wangl * @date 2018-12-06 */ @RestController public class UserController { @PostMapping("/userLogin") public ResponseVO login(Users user, HttpSession session){ //執行用戶驗證 //代碼省略....... //驗證成功后將用戶放入會話作用域 session.setAttribute("user", user); ResponseVO vo = new ResponseVO(); vo.setCode(HttpStatus.OK.value()); vo.setData("index.html"); return vo; } }
index.html(登錄成功的聊天網頁)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="js/jquery-3.3.1.min.js"></script> </head> <body> <div id="content"></div> <input type="text" id="msg" name="msg"/> <input type="button" value="send"/> <script> $(function () { var ws = new WebSocket('ws://localhost:8080/websocket'); ws.onmessage = function (event) { $('#content').append(event.data + '<br>'); } $(':button').on('click',function () { var msg = $('#msg').val(); ws.send(msg); }); }) </script> </body> </html>
運行結果:

原理參照博客:https://www.cnblogs.com/best/p/5695570.html
