1. 簡介
WebSocket是HTML5開始提供的一種在單個TCP連接上進行全雙工通訊的協議。
WebSocket的出現是為了解決Http協議只能在客戶端發送請求后服務端響應請求的問題,它允許服務端主動向客戶端發送請求。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。
在大多數情況下,為了實現消息推送,往往采用Ajax輪詢方式,它遵循的是Http協議,在特定的時間內向服務端發送請求,Http協議的請求頭較長,可能僅僅需要獲取較小的數據而需要攜帶較多的數據,而且對於消息不是特別頻繁的時候,大部分的輪詢都是無意的,造成了極大的資源浪費。
HTML5定義的WebSocket協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。
2. 本文簡要
本文基於SpringBoot框架整合WebSocket,實現三種模式發送消息:
- 自己給自己發送消息
- 自己給其他用戶發送消息
- 自己給指定用戶發送消息
3. 示例代碼
- 創建工程
- 修改pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>spring-boot-websocket-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-websocket-demo</name>
<description>Spring Boot WebSocket Demo</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 創建配置文件
在resources目錄下創建application.yml。
server:
port: 8080
spring:
thymeleaf:
prefix: classpath:/view/
suffix: .html
encoding: UTF-8
servlet:
content-type: text/html
# 生產環境設置true
cache: false
- 添加WebSocket配置類
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* WebSocket配置類
*
* @author CL
*
*/
@Configuration
public class WebSocketConfig {
/**
* 注入ServerEndpointExporter
* <p>
* 該Bean會自動注冊添加@ServerEndpoint注解的WebSocket端點
* </p>
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 創建啟動類
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 啟動類
*
* @author CL
*
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.1 自己給自己發送消息
- 創建服務端點
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* <b style="color: blue"> 自己給自己發送消息 </b>
*
* @author CL
*
*/
@Slf4j
@Component
@ServerEndpoint(value = "/selfToSelf")
public class SelfToSelfServer {
/**
* 在線數
* <p>
* 多線程環境下,為了保證線程安全
* </p>
*/
private static AtomicInteger online = new AtomicInteger(0);
/**
* 建立連接
*
* @param session 客戶端連接對象
*/
@OnOpen
public void onOpen(Session session) {
// 在線數加1
online.incrementAndGet();
log.info("客戶端連接建立成功,Session ID:{},當前在線數:{}", session.getId(), online.get());
}
/**
* 接收客戶端消息
*
* @param message 客戶端發送的消息內容
* @param session 客戶端連接對象
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("服務端接收消息成功,Session ID:{},消息內容:{}", session.getId(), message);
// 處理消息,並響應給客戶端
this.sendMessage(message, session);
}
/**
* 處理消息,並響應給客戶端
*
* @param message 客戶端發送的消息內容
* @param session 客戶端連接對象
*/
private void sendMessage(String message, Session session) {
try {
String response = "Server Response ==> " + message;
session.getBasicRemote().sendText(response);
log.info("服務端響應消息成功,接收的Session ID:{},響應內容:{}", session.getId(), response);
} catch (IOException e) {
log.error("服務端響應消息異常:{}", e.getMessage());
}
}
/**
* 關閉連接
*
* @param session 客戶端連接對象
*/
@OnClose
public void onClose(Session session) {
// 在線數減1
online.decrementAndGet();
log.info("客戶端連接關閉成功,Session ID:{},當前在線數:{}", session.getId(), online.get());
}
/**
* 連接異常
*
* @param session 客戶端連接對象
* @param error 異常
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("連接異常:{}", error);
}
}
- 創建測試頁面
在resource下創建views文件夾,並創建測試頁面selfToSelfClient.html:
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket - Self To Self</title>
</head>
<body>
<input id="message" type="text" />
<button onclick="sendMessage()">發送</button>
<button onclick="closeWebSocket()">關閉</button>
<hr/>
<div id="response"></div>
</body>
<script type="text/javascript">
var websocket = null;
// 判斷當前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/selfToSelf");
} else {
alert("當前瀏覽器不支持WebSocket");
}
// 建立連接成功時的回調方法
websocket.onopen = function (event) {
printMessage("green", "建立連接成功!!!");
}
// 連接異常時的回調方法
websocket.onerror = function (event) {
printMessage("red", "連接異常!!!");
};
// 客戶端接收消息時的回調方法
websocket.onmessage = function (event) {
printMessage("blue", event.data);
}
// 關閉連接時的回調方法
websocket.onclose = function() {
printMessage("yellow", "關閉連接成功!!!");
}
// 監聽窗口關閉事件,當窗口關閉時,主動關閉連接,防止連接未斷開時關閉窗口,服務端拋出異常
window.onbeforeunload = function() {
websocket.close();
}
// 發送消息
function sendMessage() {
if (websocket.readyState != 1) {
printMessage("red", "未建立連接或者連接已處於關閉狀態!");
} else {
var message = document.getElementById('message').value;
websocket.send(message);
}
}
// 關閉連接
function closeWebSocket() {
websocket.close();
}
// 打印消息
function printMessage(color, text) {
document.getElementById("response").innerHTML += "<font color='" + color +"'>" + text + "</font><br/>";
}
</script>
</html>
- 創建跳轉頁面Controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 頁面視圖Controller
*
* @author CL
*
*/
@Controller
public class ViewController {
/**
* 跳轉到<b> 自己給自己發送消息 </b>頁面
*
* @return
*/
@RequestMapping(value = "/selfToSelf")
public String selfToSelf() {
return "selfToSelfClient";
}
}
- 測試
- 啟動項目
- 瀏覽器訪問:http://127.0.0.1:8080/selfToSelf
- 控制台打印日志
2020-12-29 19:21:39.916 INFO 1096 --- [nio-8080-exec-2] com.c3stones.server.SelfToSelfServer : 客戶端連接建立成功,Session ID:0,當前在線數:1 2020-12-29 19:21:43.564 INFO 1096 --- [nio-8080-exec-3] com.c3stones.server.SelfToSelfServer : 服務端接收消息成功,Session ID:0,消息內容:測試1 2020-12-29 19:21:43.583 INFO 1096 --- [nio-8080-exec-3] com.c3stones.server.SelfToSelfServer : 服務端響應消息成功,接收的Session ID:0,響應內容:Server Response ==> 測試1 2020-12-29 19:21:45.290 INFO 1096 --- [nio-8080-exec-4] com.c3stones.server.SelfToSelfServer : 客戶端連接關閉成功,Session ID:0,當前在線數:0
- 瀏覽器截圖
3.2 自己給其他用戶發送消息
- 創建服務端點
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* <b style="color: blue"> 自己給其他用戶發送消息 </b>
*
* @author CL
*
*/
@Slf4j
@Component
@ServerEndpoint(value = "/selfToOther")
public class SelfToOtherServer {
/**
* 在線數
* <p>
* 多線程環境下,為了保證線程安全
* </p>
*/
private static AtomicInteger online = new AtomicInteger(0);
/**
* 在線客戶端連接集合
* <p>
* 多線程環境下,為了保證線程安全
* </p>
*/
private static Map<String, Session> onlineMap = new ConcurrentHashMap<>();
/**
* 建立連接
*
* @param session 客戶端連接對象
*/
@OnOpen
public void onOpen(Session session) {
// 在線數加1
online.incrementAndGet();
// 存放客戶端連接
onlineMap.put(session.getId(), session);
log.info("客戶端連接建立成功,Session ID:{},當前在線數:{}", session.getId(), online.get());
}
/**
* 接收客戶端消息
*
* @param message 客戶端發送的消息內容
* @param session 客戶端連接對象
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("服務端接收消息成功,Session ID:{},消息內容:{}", session.getId(), message);
// 處理消息,並響應給客戶端
this.sendMessage(message, session);
}
/**
* 處理消息,並響應給客戶端
*
* @param message 客戶端發送的消息內容
* @param session 客戶端連接對象
*/
private void sendMessage(String message, Session session) {
String response = "Server Response ==> " + message;
for (Map.Entry<String, Session> sessionEntry : onlineMap.entrySet()) {
Session s = sessionEntry.getValue();
// 過濾自己
if (!(session.getId()).equals(s.getId())) {
log.info("服務端響應消息成功,接收的Session ID:{},響應內容:{}", s.getId(), response);
s.getAsyncRemote().sendText(response);
}
}
}
/**
* 關閉連接
*
* @param session 客戶端連接對象
*/
@OnClose
public void onClose(Session session) {
// 在線數減1
online.decrementAndGet();
// 移除關閉的客戶端連接
onlineMap.remove(session.getId());
log.info("客戶端連接關閉成功,Session ID:{},當前在線數:{}", session.getId(), online.get());
}
/**
* 連接異常
*
* @param session 客戶端連接對象
* @param error 異常
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("連接異常:{}", error);
}
}
- 創建測試頁面
在resource下的views文件夾創建測試頁面selfToOtherClient.html:
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket - Self To Other</title>
</head>
<body>
<input id="message" type="text" />
<button onclick="sendMessage()">發送</button>
<button onclick="closeWebSocket()">關閉</button>
<hr/>
<div id="response"></div>
</body>
<script type="text/javascript">
var websocket = null;
// 判斷當前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/selfToOther");
} else {
alert("當前瀏覽器不支持WebSocket");
}
// 建立連接成功時的回調方法
websocket.onopen = function (event) {
printMessage("green", "建立連接成功!!!");
}
// 連接異常時的回調方法
websocket.onerror = function (event) {
printMessage("red", "連接異常!!!");
};
// 客戶端接收消息時的回調方法
websocket.onmessage = function (event) {
printMessage("blue", event.data);
}
// 關閉連接時的回調方法
websocket.onclose = function() {
printMessage("yellow", "關閉連接成功!!!");
}
// 監聽窗口關閉事件,當窗口關閉時,主動關閉連接,防止連接未斷開時關閉窗口,服務端拋出異常
window.onbeforeunload = function() {
websocket.close();
}
// 發送消息
function sendMessage() {
if (websocket.readyState != 1) {
printMessage("red", "未建立連接或者連接已處於關閉狀態!");
} else {
var message = document.getElementById('message').value;
websocket.send(message);
}
}
// 關閉連接
function closeWebSocket() {
websocket.close();
}
// 打印消息
function printMessage(color, text) {
document.getElementById("response").innerHTML += "<font color='" + color +"'>" + text + "</font><br/>";
}
</script>
</html>
- 添加跳轉頁面方法
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 頁面視圖Controller
*
* @author CL
*
*/
@Controller
public class ViewController {
/**
* 跳轉到<b> 自己給自己發送消息 </b>頁面
*
* @return
*/
@RequestMapping(value = "/selfToSelf")
public String selfToSelf() {
return "selfToSelfClient";
}
/**
* 跳轉到<b> 自己給其他用戶發送消息 </b>頁面
*
* @return
*/
@RequestMapping(value = "/selfToOther")
public String selfToOther() {
return "selfToOtherClient";
}
}
- 測試
- 啟動項目
- 瀏覽器訪問(建議打開3個頁簽):http://127.0.0.1:8080/selfToOther
- 控制台打印日志
2020-12-29 19:53:52.654 INFO 6420 --- [nio-8080-exec-2] com.c3stones.server.SelfToOtherServer : 客戶端連接建立成功,Session ID:0,當前在線數:1 2020-12-29 19:54:01.044 INFO 6420 --- [nio-8080-exec-5] com.c3stones.server.SelfToOtherServer : 客戶端連接建立成功,Session ID:1,當前在線數:2 2020-12-29 19:54:14.494 INFO 6420 --- [nio-8080-exec-8] com.c3stones.server.SelfToOtherServer : 客戶端連接建立成功,Session ID:2,當前在線數:3 2020-12-29 19:54:27.705 INFO 6420 --- [nio-8080-exec-9] com.c3stones.server.SelfToOtherServer : 服務端接收消息成功,Session ID:0,消息內容:測試1 2020-12-29 19:54:27.705 INFO 6420 --- [nio-8080-exec-9] com.c3stones.server.SelfToOtherServer : 服務端響應消息成功,接收的Session ID:1,響應內容:Server Response ==> 測試1 2020-12-29 19:54:27.753 INFO 6420 --- [nio-8080-exec-9] com.c3stones.server.SelfToOtherServer : 服務端響應消息成功,接收的Session ID:2,響應內容:Server Response ==> 測試1 2020-12-29 19:54:43.084 INFO 6420 --- [nio-8080-exec-2] com.c3stones.server.SelfToOtherServer : 服務端接收消息成功,Session ID:1,消息內容:測試2 2020-12-29 19:54:43.085 INFO 6420 --- [nio-8080-exec-2] com.c3stones.server.SelfToOtherServer : 服務端響應消息成功,接收的Session ID:0,響應內容:Server Response ==> 測試2 2020-12-29 19:54:43.086 INFO 6420 --- [nio-8080-exec-2] com.c3stones.server.SelfToOtherServer : 服務端響應消息成功,接收的Session ID:2,響應內容:Server Response ==> 測試2 2020-12-29 19:54:48.474 INFO 6420 --- [nio-8080-exec-5] com.c3stones.server.SelfToOtherServer : 服務端接收消息成功,Session ID:2,消息內容:測試3 2020-12-29 19:54:48.474 INFO 6420 --- [nio-8080-exec-5] com.c3stones.server.SelfToOtherServer : 服務端響應消息成功,接收的Session ID:0,響應內容:Server Response ==> 測試3 2020-12-29 19:54:48.475 INFO 6420 --- [nio-8080-exec-5] com.c3stones.server.SelfToOtherServer : 服務端響應消息成功,接收的Session ID:1,響應內容:Server Response ==> 測試3
- 瀏覽器截圖
3.3 自己給指定用戶發送消息
- 創建服務端點
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
/**
* <b style="color: blue"> 自己給指定用戶發送消息 </b>
*
* @author CL
*
*/
@Slf4j
@Component
@ServerEndpoint(value = "/selfToSpecific")
public class SelfToSpecificServer {
/**
* 在線數
* <p>
* 多線程環境下,為了保證線程安全
* </p>
*/
private static AtomicInteger online = new AtomicInteger(0);
/**
* 在線客戶端連接集合
* <p>
* 多線程環境下,為了保證線程安全
* </p>
*/
private static Map<String, Session> onlineMap = new ConcurrentHashMap<>();
/**
* 建立連接
*
* @param session 客戶端連接對象
*/
@OnOpen
public void onOpen(Session session) {
// 在線數加1
online.incrementAndGet();
// 存放客戶端連接
onlineMap.put(session.getId(), session);
log.info("客戶端連接建立成功,Session ID:{},當前在線數:{}", session.getId(), online.get());
}
/**
* 接收客戶端消息
*
* @param message 客戶端發送的消息內容
* @param session 客戶端連接對象
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("服務端接收消息成功,Session ID:{},消息內容:{}", session.getId(), message);
// 解析出指定用戶
JSONObject jsonObj = JSONUtil.parseObj(message);
if (jsonObj != null) {
Session s = onlineMap.get(jsonObj.get("sessionId"));
// 處理消息,並響應給客戶端
this.sendMessage(jsonObj.get("message").toString(), s);
}
}
/**
* 處理消息,並響應給客戶端
*
* @param message 客戶端發送的消息內容
* @param session 客戶端連接對象
*/
private void sendMessage(String message, Session session) {
try {
String response = "Server Response ==> " + message;
session.getBasicRemote().sendText(response);
log.info("服務端響應消息成功,接收的Session ID:{},響應內容:{}", session.getId(), response);
} catch (IOException e) {
log.error("服務端響應消息異常:{}", e.getMessage());
}
}
/**
* 關閉連接
*
* @param session 客戶端連接對象
*/
@OnClose
public void onClose(Session session) {
// 在線數減1
online.decrementAndGet();
// 移除關閉的客戶端連接
onlineMap.remove(session.getId());
log.info("客戶端連接關閉成功,Session ID:{},當前在線數:{}", session.getId(), online.get());
}
/**
* 連接異常
*
* @param session 客戶端連接對象
* @param error 異常
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("連接異常:{}", error);
}
}
- 創建測試頁面
在resource下的views文件夾創建測試頁面selfToSpecificClient.html:
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket - Self To Specific</title>
</head>
<body>
消息:<input id="message" type="text" /><br/>
Session Id:<input id="sessionId" type="text" /><br/>
<button onclick="sendMessage()">發送</button>
<button onclick="closeWebSocket()">關閉</button>
<hr/>
<div id="response"></div>
</body>
<script type="text/javascript">
var websocket = null;
// 判斷當前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/selfToSpecific");
} else {
alert("當前瀏覽器不支持WebSocket");
}
// 建立連接成功時的回調方法
websocket.onopen = function (event) {
printMessage("green", "建立連接成功!!!");
}
// 連接異常時的回調方法
websocket.onerror = function (event) {
printMessage("red", "連接異常!!!");
};
// 客戶端接收消息時的回調方法
websocket.onmessage = function (event) {
printMessage("blue", event.data);
}
// 關閉連接時的回調方法
websocket.onclose = function() {
printMessage("yellow", "關閉連接成功!!!");
}
// 監聽窗口關閉事件,當窗口關閉時,主動關閉連接,防止連接未斷開時關閉窗口,服務端拋出異常
window.onbeforeunload = function() {
websocket.close();
}
// 發送消息
function sendMessage() {
if (websocket.readyState != 1) {
printMessage("red", "未建立連接或者連接已處於關閉狀態!");
} else {
var message = document.getElementById('message').value;
var sessionId = document.getElementById('sessionId').value;
var obj = new Object();
obj.message = message;
obj.sessionId = sessionId;
websocket.send(JSON.stringify(obj));
}
}
// 關閉連接
function closeWebSocket() {
websocket.close();
}
// 打印消息
function printMessage(color, text) {
document.getElementById("response").innerHTML += "<font color='" + color +"'>" + text + "</font><br/>";
}
</script>
</html>
- 添加跳轉頁面方法
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 頁面視圖Controller
*
* @author CL
*
*/
@Controller
public class ViewController {
/**
* 跳轉到<b> 自己給自己發送消息 </b>頁面
*
* @return
*/
@RequestMapping(value = "/selfToSelf")
public String selfToSelf() {
return "selfToSelfClient";
}
/**
* 跳轉到<b> 自己給其他用戶發送消息 </b>頁面
*
* @return
*/
@RequestMapping(value = "/selfToOther")
public String selfToOther() {
return "selfToOtherClient";
}
/**
* 跳轉到<b> 自己給指定用戶發送消息 </b>頁面
*
* @return
*/
@RequestMapping(value = "/selfToSpecific")
public String selfToSpecific() {
return "selfToSpecificClient";
}
}
- 測試
- 啟動項目
- 瀏覽器訪問(打開2個頁簽):http://127.0.0.1:8080/selfToSpecific
- 控制台打印日志
2020-12-29 20:27:47.043 INFO 9004 --- [nio-8080-exec-2] c.c3stones.server.SelfToSpecificServer : 客戶端連接建立成功,Session ID:0,當前在線數:1 2020-12-29 20:27:49.558 INFO 9004 --- [nio-8080-exec-4] c.c3stones.server.SelfToSpecificServer : 客戶端連接建立成功,Session ID:1,當前在線數:2 2020-12-29 20:27:56.886 INFO 9004 --- [nio-8080-exec-7] c.c3stones.server.SelfToSpecificServer : 服務端接收消息成功,Session ID:0,消息內容:{"message":"測試1","sessionId":"1"} 2020-12-29 20:28:06.785 INFO 9004 --- [nio-8080-exec-7] c.c3stones.server.SelfToSpecificServer : 服務端響應消息成功,接收的Session ID:1,響應內容:Server Response ==> 測試1 2020-12-29 20:28:19.007 INFO 9004 --- [io-8080-exec-10] c.c3stones.server.SelfToSpecificServer : 服務端接收消息成功,Session ID:1,消息內容:{"message":"測試2","sessionId":"0"} 2020-12-29 20:28:19.008 INFO 9004 --- [io-8080-exec-10] c.c3stones.server.SelfToSpecificServer : 服務端響應消息成功,接收的Session ID:0,響應內容:Server Response ==> 測試2
- 瀏覽器截圖