当前位置:   article > 正文

【UE C++】虚幻引擎WebSocket简单应用_ue websocket

ue websocket

Websocket简介

网站上的即时通讯是很常见的,比如网页的QQ,聊天系统等。按照以往的技术能力通常是采用轮询、Comet技术解决。

HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。当需要即时通讯时,通过轮询在特定的时间间隔(如1秒),由浏览器向服务器发送Request请求,然后将最新的数据返回给浏览器。这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据 需要付出巨大的代价,是很不合算的,占用了很多的宽带。

缺点:会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上

然而WebSocket的出现可以弥补这一缺点。在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。

原理

WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的

相同点

  1. 都是一样基于TCP的,都是可靠性传输协议。

  2. 都是应用层协议。

不同点

  1. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。

  2. WebSocket是需要握手进行建立连接的。

UE的WebSocket模块

UE已对Websocket做过一层封装,其底层依赖c++ libwebsocket
官方文档 链接: Unreal WebSockets

源码路径 Source / Runtime / Online / Websockets

我们在此基础上封装上层接口,达到在UE中作为Client端使用Websockets的目的。

在工程中添加WS模块

打开UE C++工程

Build.cs

打开工程名.Build.cs文件 在Public或Private模块集中添加WebSockets模块

添加模块

我的思路是将对Iwebsocket的上层应用封装到UE组件,在要使用ws的Actor中新增此组件以获得websocket能力。
在正常建立ws链接后,将ws实例添加到GameInstance中,以便在整个游戏实例生命周期中管理WS的状态 (建立连接,获取/发送消息,在游戏结束时关闭链接。
)

新增C++类

打开编辑器,在合适的位置新建所需要的C++类

**请添加图片描述
**

请添加图片描述请添加图片描述

新建C++类 GameInstance 继承自GameInstance

请添加图片描述

编写WebSocket UActorComponent代码

UTestWebsockets 头文件

// An highlighted block
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "IWebSocket.h"
#include "Components/ActorComponent.h"
#include "TestWebsockets.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class TEST2023_API UTestWebsockets : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UTestWebsockets();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

	// 是否初始化进行连接
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	bool InitSocket;

	// Websocket连接 URL
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FString WebSocketURL;

	// 最近一次接收到的信息
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FString CurrentMessage;
	
	// 建立连接的WebSocket对象
	TSharedPtr<IWebSocket> WebSocket;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	/// @method 对WebSocket服务端发来的消息处理
	/// @param Message 服务端发来的信息
	void OnMessage(const FString& Message);
	
	/// @method 对WebSocket建立连接
	/// @param URL 服务端URL  ws:xxx  wss:xx
	/// @author xzl0527
	UFUNCTION(BlueprintCallable, Category = "SFWebSocket")
	void InitWebSocket(const FString& URL);

	/// @method 断开WebSocket连接
	UFUNCTION(BlueprintCallable, Category = "SFWebSocket")
	void CloseWebSocket();
	
	void OnPacketReceived(const void* Data, int Size, int BytesRemaining);
};

  • 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

UTestWebsockets cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "TestWebsockets.h"

#include "TestGameInstance.h"
#include "WebSocketsModule.h"

// Sets default values for this component's properties
UTestWebsockets::UTestWebsockets()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;

	// ...
}


// Called when the game starts
void UTestWebsockets::BeginPlay()
{
	Super::BeginPlay();

	// 如果设置了默认连接
	if (InitSocket)
	{
		InitWebSocket(WebSocketURL);
	}
}


// Called every frame
void UTestWebsockets::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// ...
}


void UTestWebsockets::OnMessage(const FString& Message)
{
	if (!Message.IsEmpty())
	{
		// 打印消息长度和内容
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, FString::FromInt(Message.Len()) + Message);
		CurrentMessage = Message;
	}
}

void UTestWebsockets::InitWebSocket(const FString& URL)
{
	TArray<FString> Protocols;
	Protocols.Add("ws");
	// Protocols.Add(TEXT("rx_buffer_size=10 * 1024 * 1024"));
	WebSocket = FWebSocketsModule::Get().CreateWebSocket(URL);
	InitSocket = true;
	// 成功连接时
	WebSocket->OnConnected().AddLambda([this, URL]()
	{
		// 
		const FString& Msg =
			"{mode:2,kafka_type:2,message:'Connected'}";
		WebSocket->Send(Msg);
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, URL + ":WebsocketSuccessConnected");
	});

	// 消息监听
	WebSocket->OnMessage().AddLambda([this](const FString& Message)
	{
		OnMessage(Message);
	});

	// 消息监听
	WebSocket->OnRawMessage().AddLambda([this](const void* Data, SIZE_T Size, SIZE_T BytesRemaining)
	{
		// GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Blue, FString::FromInt(BytesRemaining));
		OnPacketReceived(Data, Size, BytesRemaining);
	});


	WebSocket->Connect();


	UTestGameInstance* GameInstance = Cast<UTestGameInstance>(GetWorld()->GetGameInstance());
	if (IsValid(GameInstance))
	{
		GameInstance->SocketComponents.AddUnique(this);
	}
}

void UTestWebsockets::CloseWebSocket()
{
	// Destroy前如有连接 断开  在Instance shutdown时调用
	if (WebSocket->IsConnected())
	{
		WebSocket->Close();
	}
}

void UTestWebsockets::OnPacketReceived(const void* Data, int Size, int BytesRemaining)
{
	GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, FString::FromInt(Size));
}

  • 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
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

编写GameInstance代码

TestGameInstance 头文件

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "TestWebsockets.h"
#include "Engine/GameInstance.h"
#include "TestGameInstance.generated.h"

/**
 * 
 */
UCLASS()
class TEST2023_API UTestGameInstance : public UGameInstance
{
	GENERATED_BODY()


public:

	// WebSocket组件数组
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<UTestWebsockets*> SocketComponents;

	// 处理游戏实例关闭时逻辑
	// 1.关闭所有Websocket连接
	virtual void Shutdown() override;

	// 处理游戏实例初始化时逻辑
	virtual void Init() override;
};

  • 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

TestGameInstance cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "TestGameInstance.h"

// 处理游戏实例初始化时逻辑
void UTestGameInstance::Init()
{
	Super::Init();
	// 如果WS未成功加载 
	if (!FModuleManager::Get().IsModuleLoaded("WebSockets"))
	{
		FModuleManager::Get().LoadModule("WebSockets");
	}
}


// 处理游戏实例关闭时逻辑
// 1.关闭所有Websocket连接
void UTestGameInstance::Shutdown()
{
	if (!SocketComponents.IsEmpty())
	{
		TArray<UActorComponent*> Components(SocketComponents);
		for (UActorComponent* Component : Components)
		{
			UTestWebsockets* WebSocketComponent = Cast<UTestWebsockets>(Component);
			WebSocketComponent->CloseWebSocket();
		}
	}
	Super::Shutdown();
}

  • 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

GameInstance还需在项目设置中选择我们自己写的新类才能生效
选择此Instance,启动游戏即生效

请添加图片描述

新增业务蓝图类

1.在编辑器中合适位置新建一个继承自Actor的蓝图类
2.在组件中新增TestWebsocket组件
3.在蓝图参数设置中设置初始化及websockets url 可参考下面的nodejs代码 把URL设置成ws://127.0.0.1:4000
4.在蓝图中编写业务逻辑代码 (获取CurrentMessage后续做业务上的动作)
5.把此蓝图类拖入关卡,启动游戏即生效

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

测试

nodejs搭建简单websocket server

1.新建一个js文件
2.执行 npm i ws
3.编写下列代码
4. node ./xxx.js 启动服务

// const WebSocket = require('ws')

const WebSocketServer = WebSocket.Server;

//在4000端口上打开了一个WebSocket Server,该实例由变量wss引用。
const wss =new WebSocketServer({
    port:4000,
	perMessageDeflate: {
		zlibDeflateOptions: { // See zlib defaults.
		  chunkSize: 64,
		  memLevel: 2,
		  level: 1,
		},
		clientMaxWindowBits : 8
	}
})


//如果有WebSocket请求接入,wss对象可以响应connection事件来处理这个WebSocket:
wss.on('connection',function(ws){  //在connection事件中,回调函数会传入一个WebSocket的实例,表示这个WebSocket连接。
    console.log(`[SERVER] connection()`);
    ws.on('message',function(message){  //我们通过响应message事件,在收到消息后再返回一个ECHO: xxx的消息给客户端。
        console.log(`[SERVER] Received:${message}`);
        ws.send(`ECHO:${message}` ,(err)=>{
            if(err){
                console.log(`[SERVER] error:${err}`); 
            }
        })
		
		// var fn = function(){ 

		var fn = function(){ 
			console.log("1");
			ws.send(`服务端发来的信息` ,(err)=>{
				if(err){
					console.log(`[SERVER] error:${err}`);
				}
			})
		};

		setInterval(fn,500);
		
    });

	ws.on('close',function(data){  //在connection事件中,回调函数会传入一个WebSocket的实例,表示这个WebSocket连接。
		console.log(`[SERVER] close()`);
		console.log(data)
	});
})

  • 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

启动游戏前先用node启动ws server,Instance初始化时蓝图中的ws client组件执行连接,并在屏幕中打印消息长度及内容

请添加图片描述

Server端可见成功连接,服务端接收到了我们Send出的信息,屏幕上也打印出nodejs服务发送的ws信息

请添加图片描述
请添加图片描述

常见的WebSocket协议状态码有:

1000:正常关闭
1001:终端离开
1002:协议错误
1003:数据类型错误
1005:无法接收
1006:连接关闭异常
1011:服务器遇到异常

结束游戏时执行了GameInstance 的shutdown方法,ws server收到了1000状态码,可见WS随着UE生命周期正常关闭了

把WS封装成蓝图方法

通过查看IWebsocket源码及结合UE UFuntion宏
我们可以把一些对ws实例常见的方法 如(发送/接收 RawMessage/TextMessage 关闭链接,建立链接等 )
封装成蓝图方法调用
如有需要 欢迎评论,新开一贴展示蓝图方法Dem

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/842022
推荐阅读
  

闽ICP备14008679号