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