Java 實現 Websocket 通信
代碼:https://github.com/ioufev/websocket-springboot-demo
代碼備份下載:示例就是客戶端和服務端互相發送簡單的字符串。
WebSocket
客戶端和服務端,都有6個API(準確說是4個事件2個方法),所以說客戶端和服務端是對等的。
?? onOpen()
?? onClose()
?? onError()
?? onMessage()
?? sendMessage()
?? close()
Java 端的 4個事件2個方法

js 端的 4個事件2個方法

代碼
WebSocketConfig類
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
WebSocketServer類
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
private static int onlineCount = 0;
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
private Session session;
private String sid = "";
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在線數加1
this.sid = sid;
sendMessage("conn_success");
System.out.println("新的窗口" + sid + "已連接!當前在線人數為" + getOnlineCount());
}
@OnClose
public void onClose() {
webSocketSet.remove(this); //從set中刪除
subOnlineCount(); //在線數減1
System.out.println("連接窗口" + sid + "關閉!當前在線人數為" + getOnlineCount());
}
@OnMessage
public void onMessage(String message, Session session) {
if(message.startsWith("target-")){
int index = message.indexOf(":");
String sid = message.substring(7,index);
sendInfo(message.substring(index + 1), sid);
return;
}
this.session = session;
sendMessage("服務端收到來自窗口" + sid + "發送的消息:" + message);
}
@OnError
public void onError(Session session, Throwable error) {
this.session = session;
error.printStackTrace();
}
private void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
// 群發消息
/**
* 群發自定義消息
*/
public static void sendInfo(String message, @PathParam("sid") String sid) {
System.out.println("推送消息到窗口" + sid + ",推送內容:" + message);
for (WebSocketServer item : webSocketSet) {
//這里可以設定只推送給這個sid的,為null則全部推送
if (sid == null) {
// item.sendMessage(message);
} else if (item.sid.equals(sid)) {
item.sendMessage(message);
}
}
}
public static void sendInfo2(String message) {
System.out.println("推送消息到所有窗口" + ",推送內容:" + message);
for (WebSocketServer item : webSocketSet) {
item.sendMessage(message);
}
}
/**
* 群發自定義消息
*/
public static void sendInfo2One(String message, @PathParam("sid") String sid) {
System.out.println("推送消息到窗口" + sid + ",推送內容:" + message);
for (WebSocketServer item : webSocketSet) {
if (item.sid.equals(sid)) {
item.sendMessage(message);
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
ws-demo.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>測試WS</title>
</head>
<body>
<div>隨機ID:
<span id="ws-id">
</span>
</div>
<button id="connect">開始連接</button>
<br/>
<label for="message">發送內容: </label><input id="message" type="text" placeholder="請輸入要發送的消息內容">
<br/>
<label for="target-id">發送給誰: </label><input id="target-id" type="text" placeholder="請輸入要發送給誰">
<br/>
<button id="sendButton">發送消息</button>
<br/>
<label for="back">收到消息: </label><textarea id="back" placeholder="提示消息" style="width: 600px;height: 200px"></textarea>
<br>
<button id="disconnect">斷開連接</button>
<script>
let sendButton = window.document.getElementById("sendButton");
sendButton.addEventListener("click", () => {
send();
})
document.getElementById("disconnect").addEventListener("click", () => {
closeWebSocket();
})
let websocket;
document.getElementById("connect").addEventListener("click" , () =>{
if(websocket){
closeWebSocket()
}
// 隨機整數
let random = Math.floor(Math.random()*10000);
window.document.getElementById("ws-id").innerText = random + '';
//判斷當前瀏覽器是否支持WebSocket,是則創建WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/ws/" + random);
}else {
alert('當前瀏覽器 Not support websocket')
}
//連接發生錯誤的回調方法
websocket.onerror = function () {
console.log("WebSocket連接發生錯誤");
};
//連接成功建立的回調方法
websocket.onopen = function () {
console.log("WebSocket連接成功");
}
//接收到消息的回調方法
websocket.onmessage = function (event) {
console.log(event.data);
window.document.getElementById("back").value = event.data;
}
//連接關閉的回調方法
websocket.onclose = function () {
console.log("WebSocket連接關閉");
}
})
websocket = null;
//關閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//發送消息
function send() {
let message = document.getElementById('message').value;
let target_id = document.getElementById('target-id').value;
if (target_id != ""){
websocket.send('target-' + target_id + ':' + message);
return;
}
websocket.send(message);
}
//如果websocket連接還沒斷開就關閉了窗口,后臺server端會拋異常。
//所以增加監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket連接
window.onbeforeunload = function () {
closeWebSocket();
}
</script>
</body>
</html>
服務端推送
DemoController類
import com.ioufev.websocketspringbootdemo.ws.WebSocketServer;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class DemoController {
//推送數據接口
@PostMapping("/push/{cid}")
public Map pushToWeb(@PathVariable String cid, @RequestBody String message) {
Map<String,Object> result = new HashMap<>();
WebSocketServer.sendInfo(message, cid);
result.put("code", cid);
result.put("msg", message);
return result;
}
@PostMapping("/push")
public Map pushToWeb2(@RequestBody String message) {
Map<String,Object> result = new HashMap<>();
WebSocketServer.sendInfo2(message);
result.put("msg", message);
return result;
}
}
動圖演示

過程和問題
關于 WebSocket 協議的一些資料
關于 WebSocketServer 類中的 static 關鍵字
WebSocketServer 類的作用域是原型(Prototype),而其他 Bean 的作用域是單例(Singleton)。
自動裝配其他 Bean 實例時需要一些設置
關于 WebSocket 集群轉發
分享我使用的 Redis 發布訂閱的實現,還可以使用消息隊列MQ,ZooKeeper。
關于 WebSocket 的子協議
把 WebSocket 當作傳輸層
可以傳遞應用層協議,比如:MQTT、STOMP、AMQP、OPCUA(看代碼有),或者自定義的協議,反正只要知道別人發的消息是什么意思就行。

浙公網安備 33010602011771號