眾所周知,Web 應用的交互過程通常是客戶端通過瀏覽器發出一個請求,服務器端接收請求后進行處理並返回結果給客戶端,客端瀏覽器將信息呈現。但是對於實時性要求較高、海量並發的應用,比如金融證券的實時信息,web導航應用中地理位置獲取,社交網絡的實時消息推送等。
方案一:輪詢,客戶端用js代碼每隔一定時間向服務器發送請求,這樣會造成資源浪費(浪費帶寬),在高並發的情況下還可能造成服務器奔潰。
方案二:基於Flash、AdobeFlash,通過socket實現數據信息交互,再利用Flash暴露的接口供js調用,但是Flash在移動互聯網上的支持不好,IOS和Android都不支持Flash了。
方案三:WebSocket,2014年開始,各大應用服務器和瀏覽器廠商逐步統一,J2EE7也實現了WebSocket協議,無論客戶端還是服務器都提供了對其的支持。
WebSocket介紹與原理
WebSocket 是 HTML5 一種新的協議。它實現了瀏覽器與服務器全雙工通信,能更好的節省服務器資源和帶寬並達到實時通訊,它建立在 TCP 之上,同 HTTP 一樣通過 TCP 來傳輸數據,但是它和HTTP 最大不同是:
WebSocket 是一種雙向通信協議,在基於http建立連接后,WebSocket 服務器和 browser都能主動向對方發送或接收數據,就像 Socket 一樣;WebSocket 需要類似 TCP 的客戶端和服務器端通過握手連接,連接成功后才能相互通信,實現長連接。
WebSocket 客戶端連接報文
GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
可以看到,客戶端發起的 WebSocket 連接報文類似傳統 HTTP 報文,”Upgrade:websocket”參數值表明這是 WebSocket 類型請求,“Sec-WebSocket-Key”是 WebSocket 客戶端發送的一個 base64 編碼的密文,要求服務端必須返回一個對應加密的“Sec-WebSocket-Accept”應答,否則客戶端會拋出“Error during WebSocket handshake”錯誤,並關閉連接。
WebSocket 服務端響應報文
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
“Sec-WebSocket-Accept”的值是服務端采用與客戶端一致的密鑰計算出來后返回客戶端的,“HTTP/1.1 101 Switching Protocols”表示服務端接受 WebSocket 協議的客戶端連接,經過這樣的請求-響應處理后,客戶端服務端的 WebSocket 連接握手成功, 后續就可以進行 TCP 通訊了。 --------------------- 作者:zhengholien 來源:CSDN 原文:https://blog.csdn.net/zhengholien/article/details/76696509?utm_source=copy 版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
項目實例
采用spring maven項目,開發工具eclise ,tomcat 版本8.5以上(不需要引用其它jar包)
主要jar包要求:
(tomcat8.5 以上不需要引包)
代碼實例

websocket核心配置類
package controller; import java.util.Set; import javax.websocket.Endpoint; import javax.websocket.server.ServerApplicationConfig; import javax.websocket.server.ServerEndpointConfig; /** * websockket 核心配置類,項目啟動時會自動啟動,類似與ContextListener. */ public class WebSocketConfig implements ServerApplicationConfig{ /** * 注解方式 * 掃描src下所有類@ServerEndPoint注解的類。 */ public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> arg0) { System.out.println("============="+arg0.size()); //返回 return arg0; } /** * 獲取所有以接口方式配置的webSocket類。 */ public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> arg0) { return null; } }
webSocket服務器
package controller; 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.util.HashSet; import java.util.List; import java.util.Set; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; /** * writer: holien * Time: 2017-08-01 13:00 * Intent: webSocket服務器 */ @ServerEndpoint("/webSocket/chat/{roomName}/{username}") public class WebSocketServer { // 使用map來收集session,key為roomName,value為同一個房間的用戶集合 // concurrentMap的key不存在時報錯,不是返回null private static final Map<String, Set<Session>> rooms = new ConcurrentHashMap(); private static final Map<String, String> userNameList = new ConcurrentHashMap(); @OnOpen public void connect(@PathParam("roomName") String roomName,@PathParam("username") String username, Session session) throws Exception { // 將session按照房間名來存儲,將各個房間的用戶隔離 if (!rooms.containsKey(roomName)) { // 創建房間不存在時,創建房間 Set<Session> room = new HashSet<Session>(); // 添加用戶 room.add(session); rooms.put(roomName, room); } else { // 房間已存在,直接添加用戶到相應的房間 rooms.get(roomName).add(session); } System.err.println("username"+username); System.out.println("a client has connected!"); } @OnClose public void disConnect(@PathParam("roomName") String roomName,@PathParam("userName") String userName, Session session) { rooms.get(roomName).remove(session); System.out.println("a client has disconnected!"); } @OnMessage public void receiveMsg(@PathParam("roomName") String roomName,@PathParam("username") String username, String msg, Session session) throws Exception { // 此處應該有html過濾 msg = username + ":" + msg; System.out.println(msg); // 接收到信息后進行廣播 broadcast(roomName, msg); } // 按照房間名進行廣播 public static void broadcast(String roomName, String msg) throws Exception { for (Session session : rooms.get(roomName)) { session.getBasicRemote().sendText(msg); } } }
頁面顯示
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>網絡聊天室</title> </head> <style type="text/css"> .msg_board { width: 322px; height: 100px; border: solid 1px darkcyan; padding: 5px; overflow-y: scroll; // 文字長度大於div寬度時換行顯示 word-break: break-all; } /*set srcoll start*/ ::-webkit-scrollbar { width: 10px; height: 10px; background-color: #D6F2FD; } ::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); /*border-radius: 5px;*/ background-color: #D6F2FD; } ::-webkit-scrollbar-thumb { height: 20px; /*border-radius: 10px;*/ -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); background-color: #89D7F7; } /*set srcoll end*/ </style> <body> <label>房間名</label> <input id="input_roomName" size="10" maxlength="10"> <label>用戶名</label> <input id="username" size="10" maxlength="10"> <input type="button" value="進入聊天室" onclick="initWebSocket()" /> <input type="button" value="退出聊天室" onclick="closeWs()" /><br> <div class="msg_board"></div> <input id="input_msg" size="43" maxlength="40"> <input type="button" value="發送" onclick="send_msg()" /> </body> <script type="text/javascript"> var webSocket; function send_msg() { if (webSocket != null) { var input_msg = document.getElementById("input_msg").value.trim(); if (input_msg == "") { return; } webSocket.send(input_msg); // 清除input框里的信息 document.getElementById("input_msg").value = ""; } else { alert("您已掉線,請重新進入聊天室..."); } }; function closeWs() { webSocket.close(); }; function initWebSocket() { var roomName = document.getElementById("input_roomName").value; // 房間名不能為空 if (roomName == null || roomName == "") { alert("請輸入房間名"); return; } var username = document.getElementById("username").value.trim(); if (username == "" || username==null) { alert("用戶名不能為空") return; } if ("WebSocket" in window) { // alert("您的瀏覽器支持 WebSocket!"); if (webSocket == null) { var url = "ws://localhost:8080/webSocketDemo/webSocket/chat/" + roomName+"/"+username; // 打開一個 web socket webSocket = new WebSocket(url); } else { alert("您已進入聊天室..."); } webSocket.onopen = function () { alert("已進入聊天室,暢聊吧..."); }; webSocket.onmessage = function (evt) { var msg_board = document.getElementsByClassName("msg_board")[0]; var received_msg = evt.data; var old_msg = msg_board.innerHTML; msg_board.innerHTML = old_msg + received_msg + "<br>"; // 讓滾動塊往下移動 msg_board.scrollTop = msg_board.scrollTop + 40; }; webSocket.onclose = function () { // 關閉 websocket,清空信息板 alert("連接已關閉..."); webSocket = null; document.getElementsByClassName("msg_board")[0].innerHTML = ""; }; } else { // 瀏覽器不支持 WebSocket alert("您的瀏覽器不支持 WebSocket!"); } } </script> </html>
只需要這三個文件實現多房間多用戶聊天,


