最近看到了WebSocket,難免想作些什麼小功能,而後就選擇了聊天室,首先固然先介紹什麼是WebSockethtml
WebSocket 是 HTML5 開始提供的可在單個 TCP 鏈接上進行全雙工通信的協議,其容許服務端主動向客戶端推送數據,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸前端
注意:WebSocket 和 HTTP 的區別,WebSocket雖創建在HTTP上,但屬於新的獨立協議,只是其創建鏈接的過程須要用到HTTP協議java
爲何須要WebSocket?web
解決HTTP協議的某些缺陷 ---- 通訊只能由客戶端發起。不少網站爲了實現推送技術,使用Ajax輪詢,這樣在沒有新消息的狀況下客戶端也要發送請求,勢必形成服務器的負擔,而WebSokcet能夠主動向客戶端推送消息,是全雙工通信,能更好的節省服務器資源和帶寬ajax
ws://www.baidu.com
一:客服端請求協議升級spring
GET / HTTP/1.1 Host: localhost:8080 Origin: http://127.0.0.1:8080 Connection: Upgrade // 表示要升級協議 Upgrade: websocket // 表示升級的協議是websocket Sec-WebSocket-Version: 13 // websocket版本號 Sec-WebSocket-Key: w4v7O6xFTi36lqcgctw== // 隨機生成,防止非故意的錯誤,鏈接錯了
二:服務器響應apache
HTTP/1.1 101 Switching Protocols Upgrade: websocket // 表示能夠升級對應的協議 Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUmm5OPpG2HaGWk= // 根據客戶端key用函數計算出來
三:此後開始使用WebSocket協議api
ajax輪詢:讓瀏覽器間隔幾秒就發送一次請求,來獲取最新的響應瀏覽器
long poll:保持長鏈接來阻塞輪詢。客戶端發起請求不會馬上響應,而是有數據才返回而後關閉鏈接,而後客戶端再次發起long poll周而復始tomcat
這個代碼是極簡的,適合入門理解。WebSocket是一套已經規範好的標準的API,Tomcat、Spring等都實現了這套API,下面筆者用Springboot來操做
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
@Configuration // 配置類,用來註冊服務 public class serverConfig { @Bean // 返回的bean會自動註冊進容器 public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
重點就在這裏,先說明一下:
@Component // 註解雖然單例,但仍是會建立多例 @ServerEndpoint(value = "/wechat/{username}") // 聲明爲服務器端點 public class MyServer { // 成員變量 private Session session; private String username; // 類變量 // 類變量涉及同步問題,用線程安全類 // 能夠用<String room,<String username,MyServer> >來造成房間 private static AtomicInteger onlineCount = new AtomicInteger(0); private static ConcurrentHashMap<String, MyServer> map = new ConcurrentHashMap<>(); // 鏈接 @OnOpen public void onOpen(@PathParam("username") String username, Session session) throws IOException { this.session = session; this.username = username; map.put(username, this); addOnlineCount(); sendMessageAll(username + "加入了房間,當前在線人數:" + getOnlineCount()); } // 關閉 @OnClose public void onClose() throws IOException { subOnlineCount(); map.remove(username); sendMessageAll(username + "退出了房間,當前在線人數:" + getOnlineCount()); } // 發送錯誤 @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } // 默認羣發 @OnMessage public void onMessage(String message) throws IOException { sendMessageAll(username + ":" + message); } // 羣發 private void sendMessageAll(String message) throws IOException { for (MyServer value : map.values()) { value.session.getBasicRemote().sendText(message); // 阻塞式 // this.session.getAsyncRemote().sendText(message); // 非阻塞式 } } // 私發 private void sendMessageTo(String message, String to) throws IOException { MyServer toUser = map.get(to); toUser.session.getAsyncRemote().sendText(message); } public static synchronized int getOnlineCount() { return onlineCount.get(); } public static synchronized void addOnlineCount() { MyServer.onlineCount.getAndIncrement(); } public static synchronized void subOnlineCount() { MyServer.onlineCount.getAndDecrement(); } }
筆者寫的前端不太靠譜,知道什麼意思便可~
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>登陸頁</title> </head> // 輸入名字,url傳參省事 <body> <label for="username">Username:</label> <input id="username" type="text" placeholder="請輸入暱稱"> <button id="submit" >ENTER</button> </body> <script> var submit = document.getElementById('submit'); submit.addEventListener('click',function(){ window.location.href = 'homepage.html?username=' + document.getElementById('username').value; }) </script> </html>
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>房間</title> </head> <body> <button onclick="wsClose()">退出房間</button> <br/><br/> <div id="showMessage"></div> <br/><br/> <input id="sendMessage" type="text"/> <button onclick="sendMessage()">發送消息</button> </body> <script> // 獲取url參數的暱稱 function getQueryVariable(variable) { var query = window.location.search.substring(1); var vars = query.split("&"); for (var i=0;i<vars.length;i++) { var pair = vars[i].split("="); if(pair[0] == variable){return pair[1];} } return(false); } var conn = "ws://localhost:8080/wechat/" + getQueryVariable("username"); // webSocket鏈接 var ws = new WebSocket(conn); // 鏈接錯誤要作什麼呢? ws.onerror = function () { showMessageInnerHTML("發生未知錯誤錯誤"); } // 客戶端鏈接須要幹什麼呢? ws.onopen = function () { showMessageInnerHTML("--------------------------"); } // 客戶端關閉須要幹什麼呢? ws.onclose = function () { showMessageInnerHTML("退出了當前房間"); } // 收到消息 ws.onmessage = function (even) { showMessageInnerHTML(even.data); } // 關閉瀏覽器時 window.onbeforeunload = function () { ws.wsClose(); } // 網頁上顯示消息 function showMessageInnerHTML(msg) { document.getElementById('showMessage').innerHTML += msg + '<br/>'; } // 發送消息 function sendMessage() { var msg = document.getElementById('sendMessage').value; ws.send(msg); document.getElementById('sendMessage').value = ''; } // 關閉鏈接 function wsClose() { ws.close(); } </script> </html>
不想弄前端,湊合着看吧
參考
tomcat、Spring官網均有簡介及API的詳細介紹。推薦使用後者,後者符合spring規範並且更加優雅
http://tomcat.apache.org/tomcat-9.0-doc/websocketapi/index.html