WebSocket來實現一個簡單的聊天室做範例,紀錄一下如何使用WebSocket
伺服器採用Tomcat v8.0+、JDK 1.8,IDE 為 NetBeans
需求:
- 網頁上面可以輸入名稱登入聊天室。 登入後聊天室會出現登入訊息。
- 登入後可在訊息框中輸入訊息並送出,送出後聊天室會出現訊息。
- 聊天室出現訊息指的是,任何已登入聊天室的人都會看到訊息。
- 登入後的人只能看到登入後有人打的訊息。
先來看一下檔案結構:
再來是程式碼,相關的說明都寫在註解中,
==================
webSocketTest.html :
<!DOCTYPE html> <html> <head> <title>WebSocket Test</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="js/webSocket.js"></script> </head> <body> <div> <form id="chatRoomForm" onsubmit="return false;"> 聊天室 名字: <input type="text" id="userNameInput" /> <br/> <input type="button" id="loginBtn" value="登入" /> <span id="infoWindow"></span><br/> <input type="text" id="userinput" /> <br> <input type="submit" value="送出訊息" /> </form> </div> <div id="messageDisplay"></div> </body> </html>webSocket.js :
window.onload = function () { //獲取DOM元件 var loginBtn = document.getElementById("loginBtn"); var userNameInput = document.getElementById("userNameInput"); var infoWindow = document.getElementById("infoWindow"); var userinput = document.getElementById("userinput"); var chatRoomForm = document.getElementById("chatRoomForm"); var messageDisplay = document.getElementById("messageDisplay"); var webSocket; var isConnectSuccess = false; //設置登入鈕的動作,沒有登出,登入才可發言 loginBtn.addEventListener("click", function () { //檢查有無輸入名稱 if (userNameInput.value && userNameInput.value !== "") { setWebSocket(); //設置WebSocket連接 } else { infoWindow.innerHTML = "請輸入名稱"; } }); //Submit Form時送出訊息 chatRoomForm.addEventListener("submit", function () { sendMessage(); return false; }); //使用webSocket擁有的function, send(), 送出訊息 function sendMessage() { //檢查WebSocket連接狀態 if (webSocket && isConnectSuccess) { var messageInfo = { userName: userNameInput.value, message: userinput.value } webSocket.send(JSON.stringify(messageInfo)); } else { infoWindow.innerHTML = "未登入"; } } //設置WebSocket function setWebSocket() { //開始WebSocket連線 webSocket = new WebSocket('ws://localhost:8080/WebSocketTest/websocket'); //以下開始偵測WebSocket的各種事件 //onerror , 連線錯誤時觸發 webSocket.onerror = function (event) { loginBtn.disabled = false; userNameInput.disabled = false; infoWindow.innerHTML = "登入失敗"; }; //onopen , 連線成功時觸發 webSocket.onopen = function (event) { isConnectSuccess = true; loginBtn.disabled = true; userNameInput.disabled = true; infoWindow.innerHTML = "登入成功"; //送一個登入聊天室的訊息 var firstLoginInfo = { userName : "系統", message : userNameInput.value + " 登入了聊天室" }; webSocket.send(JSON.stringify(firstLoginInfo)); }; //onmessage , 接收到來自Server的訊息時觸發 webSocket.onmessage = function (event) { var messageObject = JSON.parse(event.data); messageDisplay.innerHTML += "<br/>" + messageObject.userName + " 說 : " + messageObject.message; }; } };WebSocketEndpointTest.java:
import java.io.IOException; import java.util.ArrayList; import javax.websocket.EncodeException; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/websocket") public class WebSocketEndpointTest { //用來存放WebSocket已連接的Socket static ArrayList<Session> sessions; @OnMessage public void onMessage(String message, Session session) throws IOException, InterruptedException, EncodeException { System.out.println("User input: " + message); //session.getBasicRemote().sendText("Hello world Mr. " + message); //for (Session s : session.getOpenSessions()) { for (Session s : sessions) { //對每個連接的Client傳送訊息 if (s.isOpen()) { s.getBasicRemote().sendText(message); } } } @OnOpen public void onOpen(Session session) { //紀錄連接到sessions中 System.out.println("Client connected"); if (sessions == null) { sessions = new ArrayList<Session>(); } sessions.add(session); System.out.println("Current sessions size: " + sessions.size()); } @OnClose public void onClose(Session session) { //將連接從sessions中移除 System.out.println("Connection closed"); if (sessions == null) { sessions = new ArrayList<Session>(); } sessions.remove(session); System.out.println("Current sessions size: " + sessions.size()); } }==================
說明:
- Java採用了Annotation的寫法。
- 在Java中,Session有一個方法叫做getBasicRemote(),理論上可以傳回所有已連接的Session,但實測不管開了幾個網頁、開了幾個WebSocket連接,永遠都是回傳個數為0的Set,推測是因為每個連接都被一個新產生的Thread來處理,類似Servlet,所以每個Session都是各別的Thread的物件,
所以我在這用Static的ArrayList<Session> sessions來儲存每個連接Session。
參考:
Java Websocket API - Fetch all Websocket Session(s) to a ServerEndpoint - 不知道為什麼,如果建立了WebSocket連接,但沒使用WebSocket.send(),並且Server先發了訊息給Client端,Client端會無法收到訊息,並且Client端要再send()訊息時會出錯,錯誤訊息為:
WebSocket connection to 'ws://XXX' failed: Invalid frame header
所以此例在登入後(即建立了WebSocket連接)馬上由Client向Server發了登入訊息,
即以下訊息:
系統 說 : XXX 登入了聊天室
只要在WebScoket建立了連接以後,馬上由Client向Server發一則訊息,似乎就不會有問題了,原因不明,希望了解者能不吝賜教。
參考資料:
原始碼下載:
WebSocketTest.7z
建議作者看一下這篇文章了解一下websocket的協議方式
回覆刪除https://read01.com/zh-tw/d4oLma.html#.WyiBCyB9iM8