UE和Spring Boot的通信方式常见的有三种:REST API 、WebSocket和 gRPC。
核心特性对比
| 维度 | REST API | WebSocket | gRPC |
|---|---|---|---|
| 通信模式 | 请求-响应(单向) | 全双工双向(长连接) | 支持单向/双向流(基于 HTTP/2) |
| 连接类型 | 短连接(每次请求新建) | 长连接(一次握手,持久通信) | 长连接(复用 HTTP/2 多路复用) |
| 数据格式 | JSON / XML(文本) | 任意(通常 JSON 文本或二进制) | Protocol Buffers(二进制,高效) |
| 服务端主动推送 | ❌ 不支持 | ✅ 原生支持 | ✅ 支持(通过 Server Streaming) |
| 延迟 | 高(需完整 HTTP 请求) | 极低(毫秒级) | 极低(二进制 + HTTP/2) |
| 带宽效率 | 低(冗余 HTTP 头) | 高(仅 payload) | 极高(紧凑二进制编码) |
| UE 支持 | ✅ 内置 HttpModule | ✅ 内置 WebSocketsModule | ❌ 需集成第三方库(如 gRPC C++) |
| Spring Boot 支持 | ✅ 原生(@RestController) | ✅ 原生(WebSocketHandler) | ✅ 需额外依赖(grpc-spring-boot-starter) |
| 跨平台兼容性 | ✅ 极佳(所有设备支持 HTTP) | ✅ 良好(现代浏览器/引擎支持) | ⚠️ 较差(需编译 native 库) |
| 调试难度 | ✅ 简单(Postman、curl) | ⚠️ 中等(需专用工具) | ❌ 困难(二进制协议,需 proto 定义) |
根据上述分析我们不难发现:
- REST API适用的场景是客户端主动发起操作:登录,购买,提交表单等。不适用于高频通信和服务端主动推送等操作。
- WebSocket适合服务端主动推送系统通知,任务更新;低频到中频的状态同步等。
- gRP用于通讯有点大材小用,这里不再详细介绍
因此根据需求我们可以选择不同的通信方式来实现UE和Spring Boot的通信,这里我们只介绍WebSocket的通信方式。
使用WebSocket通信
UE端
1.启用模块(xx.Build.cs)
PublicDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "WebSockets"
});
2.创建WebSocket管理器
// WebSocketManager.h
#pragma once
#include "CoreMinimal.h"
#include "WebSocketsModule.h"
#include "IWebSocket.h"
// 声明委托类型:接收原始消息字符串
DECLARE_MULTICAST_DELEGATE_OneParam(FOnWebSocketMessageReceived, const FString&);
class FWebSocketManager
{
public:
// 公共委托,供其他系统绑定
static FOnWebSocketMessageReceived OnMessageReceived;
static void Connect(const FString& Url);
static void Disconnect();
static void SendMessage(const FString& Message);
private:
static TSharedPtr<IWebSocket> WebSocket;
static void OnConnected();
static void OnMessage(const FString& Message);
static void OnClosed(int32 StatusCode, const FString& Reason, bool bWasClean);
};
// WebSocketManager.cpp
#include "WebSocketManager.h"
#include "Misc/Paths.h"
#include "Misc/CommandLine.h"
#include "Engine/Engine.h"
// 定义静态委托实例
FOnWebSocketMessageReceived FWebSocketManager::OnMessageReceived;
TSharedPtr<IWebSocket> FWebSocketManager::WebSocket = nullptr;
void FWebSocketManager::Connect(const FString& Url)
{
if (WebSocket.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("Already connected"));
return;
}
WebSocket = FWebSocketsModule::Get().CreateWebSocket(Url, TEXT(""));
WebSocket->OnConnected().AddStatic(&FWebSocketManager::OnConnected);
WebSocket->OnMessage().AddStatic(&FWebSocketManager::OnMessage);
WebSocket->OnClosed().AddStatic(&FWebSocketManager::OnClosed);
WebSocket->Connect();
}
void FWebSocketManager::Disconnect()
{
if (WebSocket.IsValid())
{
WebSocket->Close();
WebSocket.Reset();
}
}
void FWebSocketManager::SendMessage(const FString& Message)
{
if (WebSocket.IsValid() && WebSocket->IsConnected())
{
WebSocket->Send(Message);
}
}
void FWebSocketManager::OnConnected()
{
UE_LOG(LogTemp, Log, TEXT("WebSocket connected to server"));
// 可发送认证信息
// SendMessage(TEXT("{\"cmd\":\"auth\",\"token\":\"abc123\"}"));
}
void FWebSocketManager::OnMessage(const FString& Message)
{
UE_LOG(LogTemp, Log, TEXT("Received from server: %s"), *Message);
// 🔔 广播给所有监听者
OnMessageReceived.Broadcast(Message);
// 示例:解析 JSON
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
{
FString Type = JsonObject->GetStringField("type");
if (Type == TEXT("system"))
{
// 处理系统推送
FString Content = JsonObject->GetStringField("content");
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Yellow, Content);
}
}
}
void FWebSocketManager::OnClosed(int32 StatusCode, const FString& Reason, bool bWasClean)
{
UE_LOG(LogTemp, Warning, TEXT("WebSocket closed: %s (Code: %d)"), *Reason, StatusCode);
WebSocket.Reset();
// 可选:自动重连(避免频繁重试)
// FTimerHandle Handle;
// GWorld->GetTimerManager().SetTimer(Handle, []{ FWebSocketManager::Connect(TEXT("wss://...")); }, 5.0f, false);
}
3.在游戏逻辑中调用
例如在BeginPlay()中连接:
// ChatSystem.cpp
void UChatSystem::BeginPlay()
{
Super::BeginPlay();
// 连接socket
FWebSocketManager::Connect(TEXT("ws://127.0.0.1:8080/ws"));
// 绑定到 WebSocket 接收消息的函数:HandleWebSocketMessage
FWebSocketManager::OnMessageReceived.AddUObject(this, &UChatSystem::HandleWebSocketMessage);
}
void UChatSystem::HandleWebSocketMessage(const FString& Message)
{
TSharedPtr<FJsonObject> Json;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
if (FJsonSerializer::Deserialize(Reader, Json) && Json.IsValid())
{
if (Json->GetStringField("type") == TEXT("chat"))
{
FString Content = Json->GetStringField("content");
AddChatMessage(Content);
}
}
}
// 别忘了在销毁时解绑!
void UChatSystem::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
FWebSocketManager::OnMessageReceived.RemoveAll(this);
Super::EndPlay(EndPlayReason);
}
Spring Boot端
1.添加依赖(pom.xml)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.配置WebSocket(启用+路由)
// WebSocketConfig.java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
/**
* 注册WebSocket处理器
* 创建了一个GameWebSocketHandler对象,用它来处理WebSocket连接和消息
* 将处理器映射到 /ws 路径,客户端可以通过这个端口建立websocket连接,并允许跨域访问 (开发阶段)
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 允许跨域(开发阶段),生产环境应限制 origin
registry.addHandler(new GameWebSocketHandler(), "/ws")
.setAllowedOrigins("*");
}
}
3.实现消息处理器
创建对象+JSON序列化库,来传输JSON数据
(1)定义消息DTO类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SendSocketMessage {
private String type;
private String content;
}
(2)在消息处理器中使用定义的DTO类发送JSON数据
// GameWebSocketHandler.java
@Component
public class GameWebSocketHandler extends TextWebSocketHandler {
@Autowired
private ObjectMapper objectMapper;
// 存储所有连接(实际项目建议按用户ID分组)
private static final Set<WebSocketSession> sessions = ConcurrentHashMap.newKeySet();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
System.out.println("Client connected: " + session.getId());
session.sendMessage(new TextMessage("{\"type\":\"welcome\",\"msg\":\"Connected to server\"}"));
}
// 处理接收到的消息
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
System.out.println("Received from client: " + payload);
// 回显消息(或处理业务逻辑)
session.sendMessage(new TextMessage("{\"echo\":" + payload + "}"));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
System.out.println("Client disconnected: " + session.getId());
}
// 提供全局广播方法(用于系统推送)
public void broadcast(Object message) {
try {
String json = objectMapper.writeValueAsString(message);
broadcastJSON(json);
} catch (JsonProcessingException e) {
System.out.println("Broadcast failed:"+e);
}
}
private static void broadcastJSON(String json) {
sessions.forEach(session -> {
try {
if (session.isOpen()) {
session.sendMessage(new TextMessage(json));
}
} catch (IOException e) {
// handle
}
});
}
}
4.消息推送示例
@PostMapping("/start")
public ResponseEntity<String> start() {
// 使用websocket 向UE端发送消息
gameWebSocketHandler.broadcast(new SendSocketMessage("moveMessage","开始移动"));
return ResponseEntity.ok("BoxMoveStart");
}
在主类添加@EnableScheduling
@SpringBootApplication
@MapperScan("org.wms.pre.mapper")
@EnableScheduling
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}
测试是否连接成功
1.UE端查看监听的OnConnected()回调函数
void FWebSocketManager::OnConnected()
{
UE_LOG(LogTemp, Log, TEXT("WebSocket connected to server"));
// 可发送认证信息
// SendMessage(TEXT("{\"cmd\":\"auth\",\"token\":\"abc123\"}"));
}
查看是否在控制台打印输出该字符串,只要
OnConnected()被触发,就说明 TCP 握手 + WebSocket 协议升级成功,通信通道已建立。
2.Spring Boot服务端查看afterConnectionEstablished是否被触发
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
System.out.println("Client connected: " + session.getId());
session.sendMessage(new TextMessage("{\"type\":\"welcome\",\"msg\":\"Connected to server\"}"));
}
只要这个方法执行,说明WebSocket握手完成,连接已经建立成功
同时在UE端查看是否收到了传来的确认消息,UE 客户端收到该消息后,可视为 双向通信验证成功
使用REST API通信
UE端
由于这个方法比较简单,且用的不多,介绍的并不详细。
简单来说就是UE端发送请求,Spring Boot接收请求并返回相应的数据。
原理与 Vue和Spring Boot通信机制相同
1.启动模块xx.Build.cs
PublicDependencyModuleNames.AddRange(new string[] {
"Http","Json"
});
2.创建Http请求函数和回调函数
SendGetHelloRequest为请求函数,OnGetHelloResponse为回调函数。
请求时只需调用SendGetHelloRequest即可,可以用tick调用或游戏刚开始运行时候启动均可以。
void AHttpCommunicationActor::SendGetHelloRequest()
{
if(!bisStartMove)
{
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(TEXT("http://localhost:8080/api/setBoxMove"));
Request->SetVerb(TEXT("POST"));
Request->SetHeader(TEXT("User-Agent"), TEXT("UE5-Client"));
Request->OnProcessRequestComplete().BindUObject(this, &AHttpCommunicationActor::OnGetHelloResponse);
Request->ProcessRequest();
}
}
void AHttpCommunicationActor::OnGetHelloResponse(FHttpRequestPtr Request, FHttpResponsePtr Response,
bool bWasSuccessful)
{
if (bWasSuccessful && Response.IsValid() && EHttpResponseCodes::IsOk(Response->GetResponseCode()))
{
// 停止每帧对后端接口数据的访问
bisStartMove = true;
UE_LOG(LogTemp, Log, TEXT("[GET] Request successful! Response code: %d"), Response->GetResponseCode());
// 查找场景中的ABoxMove Actor并调用MoveBySpline,实现人物移动
for (TActorIterator<ABoxMove> It(GetWorld()); It; ++It)
{
ABoxMove* BoxMoveActor = *It;
if (BoxMoveActor)
{
BoxMoveActor->MoveBySpline();
break; // 找到第一个就停止
}
}
}
else
{
// UE_LOG(LogTemp, Error, TEXT("[GET] Request failed! Response code: %d"),
// Response.IsValid() ? Response->GetResponseCode() : -1);
}
}
Spring Boot端
创建Controller请求处理即可
@RestController
@RequestMapping("/api")
public class ueController {
@Autowired
private GameWebSocketHandler gameWebSocketHandler;
private boolean isStart = false;
private boolean isStop = false;
@CrossOrigin
@PostMapping("/setBoxMove")
public ResponseEntity<String> setBoxMove() {
if( isStart && !isStop){
// 5s后恢复isStart的值
// 创建一个定时任务,在5秒后将isStart设为false,用来重复测试移动效果
new Thread(() -> {
try {
Thread.sleep(5000);
isStart = false;
isStop = false; // 同时重置isStop状态
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
isStop = true;
return ResponseEntity.ok("BoxMoveStart");
}
// 否则返回错误信息
return ResponseEntity.badRequest().body("BoxMoveStart need isStart true");
}
@PostMapping("/start")
public ResponseEntity<String> start() {
// isStart = true;
// 使用websocket 向UE端发送消息
gameWebSocketHandler.broadcast(new SendSocketMessage("moveMessage","开始移动"));
return ResponseEntity.ok("BoxMoveStart");
}
}
4654

被折叠的 条评论
为什么被折叠?



