虚幻引擎 5 WebSocket 专用服务器架构

BlueprintWebSocket 实现指南 • 下载方式 Fab 市集

概览

BlueprintWebSocket 是一款面向虚幻引擎的高级异步网络插件。它使用 WebSocket 网络架构实现与外部服务器的实时双工数据传输,专门针对以下场景优化: 广播服务器健康诊断指标、同步持久集群配置,并将玩家进度存档推送到远程数据库.

从以下平台下载插件: Fab 市集 ,即可在当前工作区初始化实时网络组件。

蓝图

连接到 WebSocket 服务器

使用延迟辅助节点,你可以连接到活跃的网络 socket,并立即运行自动化执行链:

使用辅助节点映射 WebSocket 连接的蓝图脚本布局

对于自定义逻辑结构,请创建显式的 socket 实例,将回调事件直接挂接到事件图表分发器:

使用手动委托分配连接的高级虚幻引擎蓝图

发送数据

将结构化字符串消息路由到 WebSocket 缓冲区的蓝图节点配置

出错时自动重连

通过在基线节点序列中定义重试参数,自动处理断线:

管理连接状态恢复循环的蓝图网络节点组

C++

添加模块

打开你的源码构建配置文件 (<ProjectName>.Build.cs),并将模块依赖添加到编译指令列表中:

PublicDependencyModuleNames.Add("BlueprintWebSocket");

引入头文件

通过将封装头文件放入你的类文件中,公开接口类型:

#include "BlueprintWebSocketWrapper.h"

创建新的 WebSocket

使用插件工厂方法实例化堆分配:

UBlueprintWebSocket* const WebSocket = UBlueprintWebSocket::CreateWebSocket();

配置 WebSocket

在调用连接握手例程之前,修改传输元数据结构与身份验证标头:

// 将提供的 map 与当前标头列表合并。
WebSocket->SetHeaders(const TMap<FString, FString> & InHeaders);

// 向标头列表添加一对 Key / Value。
WebSocket->AddHeader(const FString & Header, const FString & Value);

// 从标头列表中移除该标头
WebSocket->RemoveHeader(const FString & HeaderToRemove);

使用回调监听事件

将函数目标绑定到异步委托,安全地接收网络响应状态变更:

事件名称 签名 描述
OnConnectedEvent void Func() 当我们成功连接到 WebSocket 服务器时调用。
OnConnectionErrorEvent void Func(const FString & Error) 当我们无法连接到 WebSocket 服务器时调用。
OnCloseEvent void Func(int64 StatusCode, const FString & Reason, bool bWasClean) 当与服务器的连接已关闭时调用。
OnMessageEvent void Func(const FString & Message) 当我们收到字符串消息时调用。
OnRawMessageEvent void Func(const TArray<uint8> & Data, int32 BytesRemaining) 当我们收到二进制消息时调用。
OnMessageSentEvent void Func(const FString & Message) 在我们发送消息后立即调用。

由于这些钩子使用 Dynamic Multicast Delegates,任何绑定的监听函数都必须带有 UFUNCTION() 修饰宏,才能在引擎调用栈中保持可见:

UCLASS()
class MYGAME_API UMyClass : public UObject
{
    GENERATED_BODY()
public:
    // 我们用来绑定事件的函数
    void BindEvents();
private:
    // 回调
    UFUNCTION() void OnConnected();
    UFUNCTION() void OnConnectionError(const FString & Error);
    UFUNCTION() void OnClosed(int64 StatusCode, const FString & Reason, bool bWasClean);
    UFUNCTION() void OnMessage(const FString & Message);
    UFUNCTION() void OnRawMessage(const TArray<uint8> & Data, int32 BytesRemaining);
    UFUNCTION() void OnMessageSent(const FString & Message);
private:
    UPROPERTY()
    UBlueprintWebSocket* WebSocket;
};

void UMyClass::BindEvents()
{
    // 绑定事件,使我们的函数在事件触发时被调用。
    WebSocket->OnConnectedEvent      .AddDynamic(this, &UMyClass::OnConnected);
    WebSocket->OnConnectionErrorEvent.AddDynamic(this, &UMyClass::OnConnectionError);
    WebSocket->OnCloseEvent          .AddDynamic(this, &UMyClass::OnClosed);
    WebSocket->OnMessageEvent        .AddDynamic(this, &UMyClass::OnMessage);
    WebSocket->OnRawMessageEvent     .AddDynamic(this, &UMyClass::OnRawMessage);
    WebSocket->OnMessageSentEvent    .AddDynamic(this, &UMyClass::OnMessageSent);
}

注意: 建议在连接之前绑定你将使用的所有事件。

连接到 WebSocket 服务器

将显式的端点 URI 与通信协议子字符串直接传入连接处理循环:

WebSocket->Connect(TEXT("ws://internal-cluster.local:8500/server-nodes"), TEXT("ws"));

通过功能状态检查器内联查询状态帧:

if (WebSocket->IsConnected())
{
    // 我们已连接。
}
else
{
    // 我们未连接。
}

注意: 你不应依赖 IsConnected() 来处理连接,而应依赖 OnConnectedEvent 回调。

发送消息

使用结构化字符串或直接内存缓冲区向下游传输信息:

  1. void SendMessage(const FString & Message):用于传递文本/JSON 数据格式。
  2. void SendRawMessage(const TArray<uint8> & Message, const bool bIsBinary):用于推送压缩结构或资源文件。

实现示例:

// 我们想要发送的数据,你可以通过程序化方式获取。
const FString       StringMessage = TEXT("Hello Server");
const TArray<uint8> BinaryMessage = { 0, 1, 2, 3, 4, 5 };

// 通过我们的 WebSocket 发送。
WebSocket->SendMessage   (StringMessage);
WebSocket->SendRawMessage(BinaryMessage);

完整示例

MyClass.h

#pragma once

#include "CoreMinimal.h"
#include "MyClass.generated.h"

class UBlueprintWebSocket;

/**
 * 我们使用 WebSocket 的自定义类。
 **/
UCLASS()
class MYGAME_API UMyClass : public UObject
{
    GENERATED_BODY()
public:
    void InitializeAndConnectSocket();
private:
    UFUNCTION() 
    void OnConnected();
    UFUNCTION() 
    void OnConnectionError(const FString & Error);
    UFUNCTION() 
    void OnClosed(int64 StatusCode, const FString & Reason, bool bWasClean);
    UFUNCTION() 
    void OnMessage(const FString & Message);
    UFUNCTION()
    void OnRawMessage(const TArray<uint8> & Data, int32 BytesRemaining);
    UFUNCTION()
    void OnMessageSent(const FString & Message);
private:
    UPROPERTY()
    UBlueprintWebSocket* WebSocket;
};

MyClass.cpp

#include "MyClass.h"
#include "BlueprintWebSocketWrapper.h"

void UMyClass::InitializeAndConnectSocket()
{
    WebSocket = UBlueprintWebSocket::CreateWebSocket();
    
    WebSocket->OnConnectedEvent      .AddDynamic(this, &UMyClass::OnConnected);
    WebSocket->OnConnectionErrorEvent.AddDynamic(this, &UMyClass::OnConnectionError);
    WebSocket->OnCloseEvent          .AddDynamic(this, &UMyClass::OnClosed);
    WebSocket->OnMessageEvent        .AddDynamic(this, &UMyClass::OnMessage);
    WebSocket->OnRawMessageEvent     .AddDynamic(this, &UMyClass::OnRawMessage);
    WebSocket->OnMessageSentEvent    .AddDynamic(this, &UMyClass::OnMessageSent);

    WebSocket->AddHeader(TEXT("SomeHeader"), TEXT("SomeValue"));
    WebSocket->Connect(TEXT("ws://internal-cluster.local:8500/server-nodes"), TEXT("ws"));
}

void UMyClass::OnConnected()
{
    UE_LOG(LogTemp, Log, TEXT("We are connected!"));
    WebSocket->SendMessage(TEXT("Hello Server!"));
}

void UMyClass::OnConnectionError(const FString & Error)
{
    UE_LOG(LogTemp, Error, TEXT("Failed to connect: %s."), *Error);
}

void UMyClass::OnClosed(int64 StatusCode, const FString & Reason, bool bWasClean)
{
    UE_LOG(LogTemp, Warning, TEXT("Connection closed: %d:%s. Clean: %d"), StatusCode, *Reason, bWasClean);
}

void UMyClass::OnMessage(const FString & Message)
{
    UE_LOG(LogTemp, Log, TEXT("New message: %s"), *Message);
}

void UMyClass::OnRawMessage(const TArray<uint8> & Data, int32 BytesRemaining)
{
    UE_LOG(LogTemp, Log, TEXT("New binary message: %d bytes and %d bytes remaining."), Data.Num(), BytesRemaining);
}

void UMyClass::OnMessageSent(const FString & Message)
{
    UE_LOG(LogTemp, Log, TEXT("We just sent %s to the server."), *Message);
}

疑难排解

连接失败: SSL 错误:无法获取本地签发者证书

此网络安全问题的出现,是因为在完整打包的游戏分发版本中,平台捆绑包内省略了公共验证根证书颁发机构。请通过以下步骤解决分发包完整性问题:

  1. 将你当前使用的授权证书颁发机构文件 (.pem 格式)暂存到你的项目源码路径: <Project>/Content/Certificates/cacert.pem.
  2. 通过以下设置将目标子目录暴露给平台打包器: 项目设置 >打包 >额外要复制的非资源目录 列表数组。

支援

如需功能建议或功能支援更新,请直接通过以下邮箱向我们的核心工程团队提交开发者请求工单: pandores.marketplace@gmail.com.