ue5 开发 web socket server 实战2026

目录

[ue5.1 web socket server:](#ue5.1 web socket server:)

代码修改:

[web socket server代码:](#web socket server代码:)


ue5.1 web socket server:

github地址:

https://github.com/h2ogit/UE5-ServerWebSocket/tree/main/ServerWebSocket

https://github.com/h2ogit/UE5-ServerWebSocketLite

代码修改:

cpp 复制代码
	TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this](float DeltaTime)
	//TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([=](float Time)	

web socket server代码:

Actor.h

cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"

#include "GameFramework/Actor.h"
#include "INetWebSocket.h"
//#include "WebSocketServer.h"
#include "ServerWebSocket.h"
#include "Containers/Ticker.h"
#include "Tickable.h"

#include "MyObject.generated.h"


UCLASS(BlueprintType, Blueprintable)
class METAHUMANCHARACTERHEIXI_API AMyActor : public AActor
{
	GENERATED_BODY()

 public:
	AMyActor()
	{
		// 启用 Tick
		PrimaryActorTick.bCanEverTick = true;
	}

	UFUNCTION(BlueprintCallable, Category = "Demo")
	void Init();

	UFUNCTION(BlueprintCallable, Category = "Demo")
	void Close()
	{
		ServerSocket.Reset();
		ConnectedClients.Empty();
	}

	void HandleJsonMessage(const FString& JsonStr);

 protected:
	/** Actor Tick,每帧调用 */
	virtual void Tick(float DeltaTime) override
	{
		Super::Tick(DeltaTime);

		if (ServerSocket.IsValid())
		{
			ServerSocket->Tick();
		}
	}
 public:
	TSharedPtr<FServerWebSocket> ServerSocket;
	TArray<TSharedPtr<INetWebSocket>> ConnectedClients;
	UFUNCTION(BlueprintCallable, Category = "Demo")
	void HelloWorld();

	void SaveWav(const FString& FilePath, const TArray<uint8>& AudioBytes, int32 SampleRate, int32 NumChannels);
//
	int32 FileIndex = 0;

};

Actor.cpp

cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.


#include "MyObject.h"

#include "Engine/Engine.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"

#include "Async/Async.h"

#include "Json.h"
#include "JsonUtilities.h"
#include "Misc/Base64.h"


void AMyActor::Init() {

	ServerSocket = MakeShared<FServerWebSocket>();
	bool bStarted = ServerSocket->Init(8080, FNetWebSocketClientConnectedCallBack::CreateLambda([this](INetWebSocket* NewClient)
		{
			UE_LOG(LogTemp, Log, TEXT("New client connected"));

			TSharedPtr<INetWebSocket> ClientPtr = MakeShareable(NewClient);
			ConnectedClients.Add(ClientPtr);

			NewClient->SetReceiveCallBack(FNetWebSocketPacketReceivedCallBack::CreateLambda([this, ClientPtr](void* Data, int32 Size)
				{
					// 转成 uint8* 或 char* 用
					const uint8* Bytes = reinterpret_cast<const uint8*>(Data);

					FUTF8ToTCHAR Converter((const ANSICHAR*)Bytes, Size);
					FString JsonStr(Converter.Length(), Converter.Get());

					//FString JsonStr = FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(Bytes)));
					//UE_LOG(LogTemp, Error, TEXT("Received message from client: %s"), *Msg);

					UE_LOG(LogTemp, Log, TEXT("Recv message JSON size=%d"), Size);

					HandleJsonMessage(JsonStr);
					// 回发给客户端
					//ClientPtr->Send(Bytes, Size);
				}));
		}));

	if (!bStarted)
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to start WebSocket server"));
	}
}

void AMyActor::HandleJsonMessage(const FString& JsonStr)
{
	TSharedPtr<FJsonObject> JsonObj;
	TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonStr);

	if (!FJsonSerializer::Deserialize(Reader, JsonObj) || !JsonObj.IsValid())
	{
		UE_LOG(LogTemp, Error, TEXT("Invalid JSON"));
		return;
	}

	FString Action = JsonObj->GetStringField(TEXT("action"));

	if (Action != TEXT("synthesize"))
	{
		UE_LOG(LogTemp, Warning, TEXT("Unknown action: %s"), *Action);
		return;
	}

	// 取 base64
	FString Base64Audio = JsonObj->GetStringField(TEXT("audio"));
	//int32 ChunkId = JsonObj->GetIntegerField(TEXT("chunk_id"));

	TArray<uint8> AudioBytes;
	if (!FBase64::Decode(Base64Audio, AudioBytes))
	{
		UE_LOG(LogTemp, Error, TEXT("Base64 decode failed"));
		return;
	}

	// 保存路径
	FString SavePath = FPaths::ProjectSavedDir() /
		FString::Printf(TEXT("recev_%d.wav"), FileIndex++);

	if (FFileHelper::SaveArrayToFile(AudioBytes, *SavePath))
	{
		UE_LOG(LogTemp, Log, TEXT("Saved wav: %s (%d bytes)"),
			*SavePath, AudioBytes.Num());
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("Failed to save wav"));
	}
}
void AMyActor::SaveWav(const FString& FilePath, const TArray<uint8>& AudioBytes, int32 SampleRate, int32 NumChannels)
{
	if (AudioBytes.Num() == 0) return;

	const int32 BitsPerSample = 16;
	const int32 BlockAlign = NumChannels * BitsPerSample / 8;
	const int32 ByteRate = SampleRate * BlockAlign;
	const int32 DataSize = AudioBytes.Num();
	const int32 ChunkSize = 36 + DataSize;

	TArray<uint8> Wav;
	Wav.Reserve(44 + DataSize); // 头44字节 + PCM数据

	auto AppendInt32 = [&Wav](int32 V)
		{
			Wav.Append(reinterpret_cast<uint8*>(&V), sizeof(int32));
		};

	auto AppendInt16 = [&Wav](int16 V)
		{
			Wav.Append(reinterpret_cast<uint8*>(&V), sizeof(int16));
		};

	// --- WAV Header ---
	Wav.Append(reinterpret_cast<const uint8*>("RIFF"), 4);
	AppendInt32(ChunkSize);
	Wav.Append(reinterpret_cast<const uint8*>("WAVE"), 4);

	Wav.Append(reinterpret_cast<const uint8*>("fmt "), 4);
	AppendInt32(16);           // fmt chunk size
	AppendInt16(1);            // PCM format
	AppendInt16(NumChannels);
	AppendInt32(SampleRate);
	AppendInt32(ByteRate);
	AppendInt16(BlockAlign);
	AppendInt16(BitsPerSample);

	Wav.Append(reinterpret_cast<const uint8*>("data"), 4);
	AppendInt32(DataSize);

	// --- PCM Data ---
	Wav.Append(AudioBytes);

	FFileHelper::SaveArrayToFile(Wav, *FilePath);
}

inline void SaveWavToFile(const FString& FilePath, const TArray<uint8>& AudioBytes, int32 SampleRate, int32 NumChannels)
{
	if (AudioBytes.Num() == 0) return;

	const int32 BitsPerSample = 16;
	const int32 BlockAlign = NumChannels * BitsPerSample / 8;
	const int32 ByteRate = SampleRate * BlockAlign;
	const int32 DataSize = AudioBytes.Num();
	const int32 ChunkSize = 36 + DataSize;

	TArray<uint8> Wav;
	Wav.Reserve(44 + DataSize); // 头44字节 + PCM数据

	auto AppendInt32 = [&Wav](int32 V) { Wav.Append(reinterpret_cast<uint8*>(&V), sizeof(int32)); };
	auto AppendInt16 = [&Wav](int16 V) { Wav.Append(reinterpret_cast<uint8*>(&V), sizeof(int16)); };

	// --- WAV Header ---
	Wav.Append(reinterpret_cast<const uint8*>("RIFF"), 4);
	AppendInt32(ChunkSize);
	Wav.Append(reinterpret_cast<const uint8*>("WAVE"), 4);

	Wav.Append(reinterpret_cast<const uint8*>("fmt "), 4);
	AppendInt32(16);           // fmt chunk size
	AppendInt16(1);            // PCM format
	AppendInt16(NumChannels);
	AppendInt32(SampleRate);
	AppendInt32(ByteRate);
	AppendInt16(BlockAlign);
	AppendInt16(BitsPerSample);

	Wav.Append(reinterpret_cast<const uint8*>("data"), 4);
	AppendInt32(DataSize);

	// --- PCM Data ---
	Wav.Append(AudioBytes);

	FFileHelper::SaveArrayToFile(Wav, *FilePath);
}

//void AMyActor::Tick(float DeltaTime)
//{
//	//Super::Tick(DeltaTime);
//
//	if (ServerSocket.IsValid())
//	{
//		ServerSocket->Tick();  // 处理网络事件
//	}
//}

void AMyActor::HelloWorld()
{
	UE_LOG(LogTemp, Warning, TEXT("Hello World from AMyActor!"));

	if (GEngine)
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("Hello World from AMyActor!"));
	}

	ServerSocket = MakeShared<FServerWebSocket>();

	bool bStarted = ServerSocket->Init(8080,
		FNetWebSocketClientConnectedCallBack::CreateLambda([this](INetWebSocket* NewClient)
			{
				// 每个新连接客户端都可以包装成 TSharedPtr
				TSharedPtr<INetWebSocket> ClientPtr = MakeShareable(NewClient);

				// 保存到你的客户端列表
				ConnectedClients.Add(ClientPtr);

				// 通知封装类或蓝图
				AsyncTask(ENamedThreads::GameThread, [this, ClientPtr]()
					{
						//WebSocketServerWrapper->OnConnected.ExecuteIfBound(ClientPtr);
					});

				// 给客户端绑定消息回调
				ClientPtr->SetReceiveCallBack(FNetWebSocketPacketReceivedCallBack::CreateLambda(
					[this, ClientPtr](void* Data, int32 Size)
					{
						TArray<uint8> Buffer;
						Buffer.Append(reinterpret_cast<const uint8*>(Data), Size);

						// 切到 GameThread 执行
						AsyncTask(ENamedThreads::GameThread, [this, ClientPtr, Buffer = MoveTemp(Buffer)]() mutable
							{
								//WebSocketServerWrapper->OnMessageFromClient.ExecuteIfBound(ClientPtr, Buffer);
								//OnMessage.ExecuteIfBound(ClientPtr, Buffer);
							});
					}
				));

				// 可以在这里触发 OnConnected 委托
				//AsyncTask(ENamedThreads::GameThread, [this, ClientPtr]()
				//	{
				//		//OnConnected.ExecuteIfBound(ClientPtr);
				//	});
			})

		
	);
	//GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this]()
	//	{
	//		if (ServerSocket.IsValid())
	//		{
	//			ServerSocket->Tick();
	//		}
	//	}, 0.01f, true); // 每 0.01s 调一次

}
相关推荐
嗯嗯=2 小时前
STM32单片机学习篇6
stm32·单片机·学习
2301_797312262 小时前
学习Java42天
java·开发语言·学习
找了一圈尾巴2 小时前
智能体自演进框架-ACE(论文学习)
学习·提示词
ValidationExpression2 小时前
学习:企业标准的容器化 CI,CD 发布流程
学习·ci/cd
式5162 小时前
大模型学习基础(九)LoRA微调原理
人工智能·深度学习·学习
GISer_Jing2 小时前
2026年前端开发目标(From豆包)
前端·学习·aigc
鄭郑2 小时前
【Playwright学习笔记 02】CSS-selector定位
笔记·学习
CCPC不拿奖不改名2 小时前
python基础面试编程题汇总+个人练习(入门+结构+函数+面向对象编程)--需要自取
开发语言·人工智能·python·学习·自然语言处理·面试·职场和发展
Nan_Shu_6142 小时前
学习: Threejs (17)
学习·three.js