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 调一次

}
相关推荐
端平入洛17 小时前
delete又未完全delete
c++
端平入洛2 天前
auto有时不auto
c++
西岸行者3 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
哇哈哈20213 天前
信号量和信号
linux·c++
多恩Stone3 天前
【C++入门扫盲1】C++ 与 Python:类型、编译器/解释器与 CPU 的关系
开发语言·c++·人工智能·python·算法·3d·aigc
蜡笔小马3 天前
21.Boost.Geometry disjoint、distance、envelope、equals、expand和for_each算法接口详解
c++·算法·boost
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
超级大福宝3 天前
N皇后问题:经典回溯算法的一些分析
数据结构·c++·算法·leetcode
别催小唐敲代码3 天前
嵌入式学习路线
学习
weiabc3 天前
printf(“%lf“, ys) 和 cout << ys 输出的浮点数格式存在细微差异
数据结构·c++·算法