用Java和JavaScript基於WebSocket完成聊天室Democss
什麼是WebSocket,WebSocket是一種基於TCP的網絡協議,就像HTTP同樣,它與HTTP最大的不一樣就是它是全雙工的,也就是服務器能夠主動發送數據給瀏覽器(是否是像Java中的Socket)。在HTTP中,瀏覽器發起請求以後服務器才能響應,給瀏覽器發送數據,服務器不能主動給瀏覽器發送數據。
可是在不少時候,最簡單的就好比聊天室,在Http中只能採用輪訓的方式,也就是瀏覽器不停地訪問服務器查詢有沒有消息,這樣作效率很低,並且很是浪費流量,WebSocket就是解決「服務器沒法主動推送數據」這一難點而發明的。html
目前瀏覽器基本都支持WebSocket,這種協議有着以下特色:前端
websocket以ws
開頭,一個標準的ws網址像這樣:java
ws://ip:port/path
其中IP能夠被域名代替。下面我會給出一個websocket下的聊天室Demojquery
環境:JDK 1.8.0_211
開發工具:IDEA
項目管理工具:maven
前端頁面:bootstrapweb
代碼有詳細的註釋,應該比較好懂的。json
前端頁面:
bootstrap
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>基於WebSocket的在線聊天室</title> <link href="css/bootstrap.min.css" rel="stylesheet"> <style> .chatFont { /* 消息字體大小 */ font-size: 16px; } .input { /* 輸入框絕對定位 */ position: fixed; top: 75%; right: 3%; width: 70%; font-size: 16px; } </style> </head> <body> <!--側邊欄--> <div class="column col-xs-3" id="sidebar"> <h3 class="text-center">在線人員</h3> <ul class="nav" id="onlineUser"> <!-- 在線用戶顯示在這裏 --> </ul> </div> <div class="col-xs-9"> <div class="panel panel-info"> <div class="panel-heading"><h3>聊天室</h3></div> <div class="panel-body" id="show" style="height: 380px;"> <!-- 消息顯示在這裏 --> <div class="chatFont">聊天記錄<br></div> </div> </div> <div class="input"> <label for="msg">請輸入</label> <textarea class="form-control" rows="5" id="msg"></textarea> </div> </div> </body> <script src="js/jquery-3.4.1.js"></script> <script src="js/bootstrap.min.js"></script> <script> //這個num是用來限制消息條數,否則消息會撐破面板(前端技術太渣只能用這種笨辦法) var num = 1; // 建立WebSocket對象 var socket = new WebSocket("ws://localhost:8080/chat"); //發送消息 var sendMsg = function () { var input = $('#msg'); if (input.val()) { socket.send(input.val()); } else { alert('消息不能爲空') } //清空輸入框 input.val(""); }; //輸入框鍵盤事件檢測 var keyDown = function (e) { if (!e.ctrlKey && e.keyCode == 13) { // enter 鍵 sendMsg(); } else if (e.keyCode == 13 && e.ctrlKey) { //實現換行,這個沒寫 alert('ctrl enter'); } }; //綁定輸入框鍵盤事件 $('#msg').keydown(keyDown); //websocket監聽事件,收到消息時觸發 socket.onmessage = function (ev) { //console.log(ev); showMsg(ev.data); }; //顯示消息 var showMsg = function (data) { //後端使用Json傳輸 data = JSON.parse(data); //若是有消息則顯示在消息框 if (data.msg) { var text = '<div class="chatFont">' + '[' + data.userName + ']:' + data.msg + '<br></div>'; $('#show').append(text); } //若是是新用戶則添加在線成員,針對新用戶 if (data.onlineUser) { console.log(data.onlineUser); var online = data.onlineUser; $.each(online, function (index, element) { $('#onlineUser').append('<li class="active" id="' + element + '"><a href="#">' + element + '</a></li>'); }) } //若是有新用戶進來則添加在線成員,針對已在線成員 if (data.addUser) { $('#onlineUser').append('<li class="active" id="' + data.userName + '"><a href="#">' + data.userName + '</a></li>'); } //若是有用戶離開則移除在線成員 if (data.removeUser) { $('#' + data.userName + '').remove(); } //消息大於15條時刪除最上邊的,防止撐破消息欄 if ($('#show').children('div').length>15){ $('#show').children('div:first').remove(); } }; //鏈接斷開時觸發,清空綁定 socket.onclose = function (ev) { $('#msg').unbind(); $('#show').text('鏈接已斷開'); }; //鏈接時觸發,建立用戶名 socket.onopen = function (ev) { var name = prompt('請輸入暱稱:'); if (name) { socket.send(name); } else { socket.send('遊客:' + Math.random() * 100000000000000000); } } </script> </html>
前端技術有點水,只能高出這麼簡陋的頁面了。後端
即便這樣也要搞個漂亮的首頁😆:
api
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>首頁</title> <link href="css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="jumbotron"> <div class="container text-center" > <h2 class="text-info" style="font-family:宋體;font-weight:bold;font-size:49px">基於WebSocket的在線聊天室</h2> <br> <div class="text-muted">與世界分享你的逼格</div> <br> <br> </div> <div class="container text-center"> <button class="btn btn-primary" id="enter">進入</button> </div> </div> </body> <script src="js/jquery-3.4.1.js"></script> <script src="js/bootstrap.min.js"></script> <script> $('#enter').click(function () { location.href = 'chat.html'; }); </script> </html>
接下來是後端,首先添加依賴:
<dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.59</version> </dependency>
而後是服務端主體:
面向對象,對消息進行封裝:
public class Message { private String userName;//用戶 private String msg;//消息主體 private boolean addUser;//用戶加入 private boolean removeUser;//用戶離開 //清空狀態 public void clearMsg(){ msg = null; addUser = false; removeUser = false; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public boolean isAddUser() { return addUser; } public void setAddUser(boolean addUser) { this.addUser = addUser; } public boolean isRemoveUser() { return removeUser; } public void setRemoveUser(boolean removeUser) { this.removeUser = removeUser; } @Override public String toString() { return "Message{" + "userName='" + userName + '\'' + ", msg='" + msg + '\'' + ", addUser=" + addUser + ", removeUser=" + removeUser + '}'; } }
服務主體:
import com.alibaba.fastjson.JSON; import com.bilibili.pojo.Message; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; //訪問路徑,相似http中的@WebServlet()註解 @ServerEndpoint("/chat") public class Chat { //每個鏈接都會建立一個Chat對象,因此建立一個靜態Map來保存已鏈接用戶 private static final Map<String, Chat> clientMap = new HashMap<>(); private boolean firstFlag = true;//是否第一次訪問 private String name; private Session session;//這裏的Session和servlet中的Session不是同一個種 private Message message = new Message(); /** * 客戶端鏈接時執行的方法 * @param session 客戶端session * @throws IOException */ @OnOpen public void start(Session session) throws IOException { this.session = session; System.out.println("鏈接"); } /** * 客戶端斷開 */ @OnClose public void end() { //從鏈接對象中移除 clientMap.remove(name, this); //向全部人發送一個有人離開的消息 message.clearMsg(); message.setUserName(name); message.setMsg("離開了聊天室!"); message.setRemoveUser(true); // 發送消息 sendMsg(JSON.toJSONString(message)); System.out.println("斷開"); } /** * 服務端收到消息 * @param msg */ @OnMessage public void receive(String msg) { if (firstFlag) { //把第一次的消息做爲用戶名 name = msg; //構造發送給全部人的消息 message.setMsg("加入了聊天室!"); message.setUserName(name); message.setAddUser(true); //獲取當前在線用戶 List<String> onlineUser = new ArrayList<>(clientMap.keySet()); clientMap.put(name, this); try { //直接構造Json,給新鏈接的用戶發送刷新在線用戶的消息 session.getBasicRemote().sendText("{\"onlineUser\":"+JSON.toJSONString(onlineUser)+"}"); } catch (IOException e) { e.printStackTrace(); } // 給全部用戶發送有人進入的消息 sendMsg(JSON.toJSONString(message)); firstFlag = false; } else { //不是第一次則直接發送消息 message.clearMsg(); message.setMsg(msg); sendMsg(JSON.toJSONString(message)); } } // 當客戶端通訊出現錯誤時,激發該方法 @OnError public void onError(Throwable t) throws Throwable { System.out.println("WebSocket服務端錯誤 " + t); } //發送消息的方法 public void sendMsg(String msg) { // 遍歷服務器關聯的全部客戶端 Chat client = null; for (String nickname : clientMap.keySet()) { try { client = clientMap.get(nickname); synchronized (client) { // 發送消息 client.session.getBasicRemote().sendText(msg); } } catch (IOException e) { System.out.println("聊天錯誤,向客戶端 " + client + " 發送消息出現錯誤。"); clientMap.remove(name, client); try { client.session.close(); } catch (IOException e1) { } Message newMessage = new Message(); newMessage.setMsg("["+client.name+"]已經被斷開了鏈接。"); sendMsg(JSON.toJSONString(newMessage)); } } } }
這樣就簡單實現了一個聊天室。
效果圖以下:
這麼寫能夠發送html標籤來達到發送圖片的目的🤣