赞
踩
网站上的即时通讯是很常见的,比如网页的QQ,聊天系统等。按照以往的技术能力通常是采用轮询、Comet技术解决。
HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。当需要即时通讯时,通过轮询在特定的时间间隔(如1秒),由浏览器向服务器发送Request请求,然后将最新的数据返回给浏览器。这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据 需要付出巨大的代价,是很不合算的,占用了很多的宽带。
缺点:会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上
然而WebSocket的出现可以弥补这一缺点。在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。
原理
WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的
相同点
都是一样基于TCP的,都是可靠性传输协议。
都是应用层协议。
不同点
WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
WebSocket是需要握手进行建立连接的。
UE已对Websocket做过一层封装,其底层依赖c++ libwebsocket库
官方文档 链接: Unreal WebSockets
源码路径 Source / Runtime / Online / Websockets
我们在此基础上封装上层接口,达到在UE中作为Client
端使用Websockets的目的。
打开UE C++
工程
打开工程名.Build.cs文件 在Public或Private模块集中添加WebSockets模块
我的思路是将对Iwebsocket的上层应用封装到UE组件,在要使用ws的Actor中新增此组件以获得websocket能力。
在正常建立ws链接后,将ws实例添加到GameInstance中,以便在整个游戏实例生命周期中管理WS的状态 (建立连接,获取/发送消息,在游戏结束时关闭链接。)
打开编辑器,在合适的位置新建所需要的C++类
**
**
新建C++类 GameInstance 继承自GameInstance
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);
};
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));
}
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;
};
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();
}
GameInstance还需在项目设置中选择我们自己写的新类才能生效
选择此Instance,启动游戏即生效
1.在编辑器中合适位置新建一个继承自Actor的蓝图类
2.在组件中新增TestWebsocket组件
3.在蓝图参数设置中设置初始化及websockets url 可参考下面的nodejs代码 把URL设置成ws://127.0.0.1:4000
4.在蓝图中编写业务逻辑代码 (获取CurrentMessage后续做业务上的动作)
5.把此蓝图类拖入关卡,启动游戏即生效
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)
});
})
启动游戏前先用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生命周期正常关闭了
通过查看IWebsocket源码及结合UE UFuntion宏
我们可以把一些对ws实例常见的方法 如(发送/接收 RawMessage/TextMessage 关闭链接,建立链接等 )
封装成蓝图方法调用
如有需要 欢迎评论,新开一贴展示蓝图方法Dem
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。