SpringBoot整合WebSocket 打造聊天室
阿新 • • 發佈:2018-12-23
SpringBoot 整合 WebSocket
什麼是WebSocket?
WebSocket是HTML5開始提供的一種瀏覽器與伺服器間進行全雙工通訊的網路技術。依靠這種技術可以實現客戶端和伺服器端的長連線,雙向實時通訊。
WebSocket協議被設計來取代用HTTP作為傳輸層的雙向通訊技術,基於大部分Web服務都是HTTP協議,WebSocket仍使用HTTP來作為初始的握手(handshake),在握手中對HTTP協議進行升級,當服務端收到這個HTTP的協議升級請求後,如果支援WebSocket協議則返回HTTP狀態碼101,這樣,WebSocket的握手便成功了。
特點
非同步、事件觸發
可以傳送文字,圖片等流檔案
資料格式比較輕量,效能開銷小,通訊高效
使用ws或者wss協議的客戶端socket,能夠實現真正意義上的推送功能
缺點:
部分瀏覽器不支援,瀏覽器支援的程度與方式有區別,需要各種相容寫法。
長連線
與 AJAX 輪訓的方式差不多,但長連線不像 AJAX 輪訓一樣,而是採用的阻塞模型(一直打電話,沒收到就不掛電話);客戶端發起連線後,如果沒訊息,就一直不返回 Response 給客戶端。直到有訊息才返回,返回完之後,客戶端再次建立連線,周而復始。
在沒有 WebSocket 之前,大家常用的手段應該就是輪訓了,比如每隔幾秒發起一次請求,但這樣帶來的就是高效能開銷,都知道一次 HTTP 響應是需要經過三次握手和四次揮手,遠不如 TCP 長連線來的划算。
WebSocket 事件
與SpringBoot整合Demo
匯入jar包,
compile group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '2.0.4.RELEASE'
宣告服務端點
@RestController
@ServerEndpoint("/chat-room/{username}")
public class ChatRoomServerEndpoint {
private static final Logger log = LoggerFactory.getLogger(ChatRoomServerEndpoint.class);
/**
* Open session.
* 連線服務端,開啟session
*
* @param username the 使用者名稱
* @param session the 會話
*/
@OnOpen
public void openSession(@PathParam("username") String username, Session session) {
LIVING_SESSIONS_CACHE.put(username, session);
String message = "歡迎使用者[" + username + "] 來到聊天室!";
log.info(message);
sendMessageAll(message);
sendAllUser();
}
/**
* On message.
* 向客戶端推送訊息
*
* @param username the 使用者名稱
* @param message the 訊息
*/
@OnMessage
public void onMessage(@PathParam("username") String username, String message) {
log.info("{}傳送訊息:{}", username, message);
sendMessageAll("使用者[" + username + "] : " + message);
}
/**
* On close.
* 連線關閉
*
* @param username the 使用者名稱
* @param session the 會話
*/
@OnClose
public void onClose(@PathParam("username") String username, Session session) {
//當前的Session 移除
LIVING_SESSIONS_CACHE.remove(username);
//並且通知其他人當前使用者已經離開聊天室了
sendMessageAll("使用者[" + username + "] 已經離開聊天室了!");
sendAllUser();
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* On error.
* 出現錯誤
*
* @param session the session
* @param throwable the throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
throwable.printStackTrace();
}
/**
* On message.
* 點到點推送訊息
*
* @param sender the 傳送至
* @param receive the 接受者
* @param message the 訊息
*/
@GetMapping("/chat-room/{sender}/to/{receive}")
public void onMessage(@PathVariable("sender") String sender, @PathVariable("receive") String receive, String message) {
sendMessage(LIVING_SESSIONS_CACHE.get(receive), MessageType.MESSAGE,
"[" + sender + "]" + "-> [" + receive + "] : " + message);
log.info("[" + sender + "]" + "-> [" + receive + "] : " + message);
}
}
使用列舉類區分推送給前端類訊息型別
public enum MessageType {
/**
* 使用者名稱.
*/
USERNAME("username"),
/**
* 普通訊息.
*/
MESSAGE("message");
private String value;
public String getValue() {
return value;
}
MessageType(String value) {
this.value = value;
}
}
推送訊息工具類
public final class WebSocketUtils {
/**
* 模擬儲存 websocket session 使用
*/
public static final Map<String, Session> LIVING_SESSIONS_CACHE = new ConcurrentHashMap<>();
/**
* Send 訊息至客戶端
*
* @param message the message
*/
public static void sendMessageAll(String message) {
LIVING_SESSIONS_CACHE.forEach((sessionId, session) -> sendMessage(session, MessageType.MESSAGE, message));
}
/**
* Send 所有使用者名稱至客戶端
*/
public static void sendAllUser() {
LIVING_SESSIONS_CACHE.forEach((sessionId, session) -> sendMessage(session,
MessageType.USERNAME, LIVING_SESSIONS_CACHE.keySet()));
}
/**
* 傳送給指定使用者訊息
*
* @param session 使用者 session
* @param type the type
* @param message 傳送內容
*/
public static void sendMessage(Session session, MessageType type, Object message) {
if (session == null) {
return;
}
final RemoteEndpoint.Basic basic = session.getBasicRemote();
if (basic == null) {
return;
}
try {
String data = JSONParseUtils.object2JsonString(ApiResult.prepare().success(message, 200, type.getValue()));
//向session推送資料
basic.sendText(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
配置WebSocket
@EnableWebSocket
@Configuration
public class WebSocketConfiguration {
/**
* Server endpoint exporter server endpoint exporter.
*
* @return the server endpoint exporter
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
聊天室頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>基於WebSocket 簡易聊天室</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script type="text/javascript" src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<form class="form-horizontal col-sm-8" role="form">
<h2 class="center">聊 天 室 </h2>
<div class="form-group">
<label for="message_content" class="col-sm-1 control-label">訊息列表</label>
<div class="col-sm-4">
<textarea id="message_content" class="form-control" readonly="readonly"
cols="57" rows="10">
</textarea>
</div>
<label for="user-list" class="col-sm-1 control-label">使用者列表</label>
<div class="col-sm-2">
<textarea id="user-list" readonly="readonly" class="form-control"
cols="8" rows="10"></textarea>
</div>
</div>
<div class="form-group">
<label for="in_user_name" class="col-sm-2 control-label">使用者姓名 </label>
<div class="col-sm-8">
<input id="in_user_name" class="form-control" value=""/>
</div>
<button type="button" id="btn_join" class="btn btn-default">加入聊天室</button>
<button type="button" id="btn_exit" class="btn btn-danger">離開聊天室</button>
</div>
<div class="form-group">
<label for="in_room_msg" class="col-sm-2 control-label">群發訊息 </label>
<div class="col-sm-8">
<input id="in_room_msg" class="form-control" value=""/>
</div>
<button type="button" id="btn_send_all" class="btn btn-default">傳送訊息</button>
</div>
<br/><br/><br/>
<h3 class="center">好友聊天</h3>
<br/>
<div class="form-group">
<label for="in_sender" class="col-sm-2 control-label">傳送者 </label>
<div class="col-sm-8">
<input id="in_sender" class="form-control" value=""/>
</div>
</div>
<div class="form-group">
<label for="in_receive" class="col-sm-2 control-label">接受者 </label>
<div class="col-sm-8">
<input id="in_receive" class="form-control" value=""/>
</div>
</div>
<div class="form-group">
<label for="in_point_message" class="col-sm-2 control-label">傳送訊息 </label>
<div class="col-sm-8">
<input id="in_point_message" class="form-control" value=""/>
</div>
<button type="button" class="btn btn-default" id="btn_send_point">傳送訊息</button>
</div>
</form>
</body>
<script type="text/javascript">
$(document).ready(function () {
var urlPrefix = 'ws://localhost:8080/chat-room/';
var ws = null;
$('#btn_join').click(function () {
var username = $('#in_user_name').val();
var url = urlPrefix + username;
ws = new WebSocket(url);
ws.onopen = function () {
console.log("建立 websocket 連線...");
};
ws.onmessage = function (event) {
var data = JSON.parse(event.data);
console.info(data);
if (data.msg === "message") {
//服務端傳送的訊息
$('#message_content').append(data.result + '\n');
} else if (data.msg === "username") {
var result = '';
$.each(data.result, function (index, value) {
result += value + '\n';
})
$('#user-list').text(result);
}
};
ws.onclose = function () {
$('#message_content').append('使用者[' + username + '] 已經離開聊天室!');
console.log("關閉 websocket 連線...");
}
});
//客戶端傳送訊息到伺服器
$('#btn_send_all').click(function () {
var msg = $('#in_room_msg').val();
if (ws) {
ws.send(msg);
}
});
// 退出聊天室
$('#btn_exit').click(function () {
if (ws) {
ws.close();
}
});
$("#btn_send_point").click(function () {
var sender = $("#in_sender").val();
var receive = $