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)
1 2 3
| PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "WebSockets" });
|
2.创建WebSocket管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #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); };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #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")); }
void FWebSocketManager::OnMessage(const FString& Message) { UE_LOG(LogTemp, Log, TEXT("Received from server: %s"), *Message); OnMessageReceived.Broadcast(Message);
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();
}
|
3.在游戏逻辑中调用
例如在BeginPlay()中连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| void UChatSystem::BeginPlay() { Super::BeginPlay(); FWebSocketManager::Connect(TEXT("ws://127.0.0.1:8080/ws")); 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)
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
|
2.配置WebSocket(启用+路由)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {
@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new GameWebSocketHandler(), "/ws") .setAllowedOrigins("*"); } }
|
3.实现消息处理器
创建对象+JSON序列化库,来传输JSON数据
(1)定义消息DTO类
1 2 3 4 5 6 7 8 9 10
| @Data @AllArgsConstructor @NoArgsConstructor public class SendSocketMessage {
private String type; private String content;
}
|
(2)在消息处理器中使用定义的DTO类发送JSON数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| @Component public class GameWebSocketHandler extends TextWebSocketHandler {
@Autowired private ObjectMapper objectMapper;
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) { } }); } }
|
4.消息推送示例
1 2 3 4 5 6
| @PostMapping("/start") public ResponseEntity<String> start() { gameWebSocketHandler.broadcast(new SendSocketMessage("moveMessage","开始移动")); return ResponseEntity.ok("BoxMoveStart"); }
|
在主类添加@EnableScheduling
1 2 3 4 5 6 7 8
| @SpringBootApplication @MapperScan("org.wms.pre.mapper") @EnableScheduling public class BackendApplication { public static void main(String[] args) { SpringApplication.run(BackendApplication.class, args); } }
|
测试是否连接成功
1.UE端查看监听的OnConnected()回调函数
1 2 3 4 5 6
| void FWebSocketManager::OnConnected() { UE_LOG(LogTemp, Log, TEXT("WebSocket connected to server")); }
|
查看是否在控制台打印输出该字符串,只要 OnConnected() 被触发,就说明 TCP 握手 + WebSocket 协议升级成功,通信通道已建立。
2.Spring Boot服务端查看afterConnectionEstablished是否被触发
1 2 3 4 5 6
| @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
1 2 3
| PublicDependencyModuleNames.AddRange(new string[] { "Http","Json" });
|
2.创建Http请求函数和回调函数
SendGetHelloRequest为请求函数,OnGetHelloResponse为回调函数。
请求时只需调用SendGetHelloRequest即可,可以用tick调用或游戏刚开始运行时候启动均可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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()); for (TActorIterator<ABoxMove> It(GetWorld()); It; ++It) { ABoxMove* BoxMoveActor = *It; if (BoxMoveActor) { BoxMoveActor->MoveBySpline(); break; } } } else { } }
|
Spring Boot端
创建Controller请求处理即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @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){ new Thread(() -> { try { Thread.sleep(5000); isStart = false; isStop = false; } 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() {
gameWebSocketHandler.broadcast(new SendSocketMessage("moveMessage","开始移动")); return ResponseEntity.ok("BoxMoveStart"); }
}
|