websocket實現多房間聊天室
眾所周知,Web 應用的互動過程通常是客戶端通過瀏覽器發出一個請求,伺服器端接收請求後進行處理並返回結果給客戶端,客端瀏覽器將資訊呈現。但是對於實時性要求較高、海量併發的應用,比如金融證券的實時資訊,web導航應用中地理位置獲取,社交網路的實時訊息推送等。
方案一:輪詢,客戶端用js程式碼每隔一定時間向伺服器傳送請求,這樣會造成資源浪費(浪費頻寬),在高併發的情況下還可能造成伺服器奔潰。
方案二:基於Flash、AdobeFlash,通過socket實現資料資訊互動,再利用Flash暴露的介面供js呼叫,但是Flash在移動網際網路上的支援不好,IOS和Android都不支援Flash了。
方案三
WebSocket介紹與原理
WebSocket 是 HTML5 一種新的協議。它實現了瀏覽器與伺服器全雙工通訊,能更好的節省伺服器資源和頻寬並達到實時通訊,它建立在 TCP 之上,同 HTTP 一樣通過 TCP 來傳輸資料,但是它和HTTP 最大不同是:
WebSocket 是一種雙向通訊協議,在基於http建立連線後,WebSocket 伺服器和 browser都能主動向對方傳送或接收資料,就像 Socket 一樣;WebSocket 需要類似 TCP 的客戶端和伺服器端通過
WebSocket 客戶端連線報文
GET /webfin/websocket/ HTTP/1.1Host: localhost
Upgrade: websocketConnection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==Origin: http://localhost:8080
Sec-WebSocket-Version: 13
可以看到,客戶端發起的 WebSocket 連線報文類似傳統 HTTP 報文,”Upgrade:websocket”引數值表明這是 WebSocket 型別請求,WebSocket 服務端響應報文
HTTP/1.1 101 Switching ProtocolsUpgrade: websocket
Connection: UpgradeSec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
“Sec-WebSocket-Accept”的值是服務端採用與客戶端一致的金鑰計算出來後返回客戶端的,“HTTP/1.1 101 Switching Protocols”表示服務端接受 WebSocket 協議的客戶端連線,經過這樣的請求-響應處理後,客戶端服務端的 WebSocket 連線握手成功, 後續就可以進行 TCP 通訊了。下載javax.websocket.jar,使用註解方式實現了一個簡單的多房間聊天demo,demo只有一個服務端類和一個前端chat.html頁面,開啟多個chat.html頁面,輸入相同的房間名,進入房間後可以相互通訊,不同房間不能互相通訊,不同使用者我用websocket的session自己分配的id來區分,因為一個使用者連線到webSocket伺服器就對應一個session,實際開發可以用http的session中登入的使用者名稱來區分,連線到伺服器的url中,roomName是一個路徑引數,即在chat.html中獲取到房間名。多房間的原理其實就是把多個用(session)放在roomName對應的set集合中,每次廣播資訊只在房間名對應的set集合中廣播,實現房間聊天資訊的隔離。
程式碼如下:
package webSocketTest;
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.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}")
public class WsServer {
// 使用map來收集session,key為roomName,value為同一個房間的使用者集合
// concurrentMap的key不存在時報錯,不是返回null
private static final Map<String, Set<Session>> rooms = new ConcurrentHashMap();
@OnOpen
public void connect(@PathParam("roomName") String roomName, Session session) throws Exception {
// 將session按照房間名來儲存,將各個房間的使用者隔離
if (!rooms.containsKey(roomName)) {
// 建立房間不存在時,建立房間
Set<Session> room = new HashSet<>();
// 新增使用者
room.add(session);
rooms.put(roomName, room);
} else {
// 房間已存在,直接新增使用者到相應的房間
rooms.get(roomName).add(session);
}
System.out.println("a client has connected!");
}
@OnClose
public void disConnect(@PathParam("roomName") String roomName, Session session) {
rooms.get(roomName).remove(session);
System.out.println("a client has disconnected!");
}
@OnMessage
public void receiveMsg(@PathParam("roomName") String roomName,
String msg, Session session) throws Exception {
// 此處應該有html過濾
msg = session.getId() + ":" + 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">
<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;
}
if ("WebSocket" in window) {
// alert("您的瀏覽器支援 WebSocket!");
if (webSocket == null) {
var url = "ws://localhost:8080/webSocket/chat/" + roomName;
// 開啟一個 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>
同時開啟3個chat.html頁面,2個頁面進入名為“技術分享會”的房間,剩下一個進入名為“spring原始碼分析”的房間,只有相同房間的資訊可以互通,截圖如下:
發覺要更好的學習一種技術,得把該技術運用在具體功能實現上,這樣會迫使我們去找資料和API,使得對該技術的認識更深刻...