How to properly implement IWebSocket and send packets with high frequency?

Hello.

I am creating a UE plugin that can create a websocket connection and send/receive packets. I was using libwebsocket before which was working fine. I have multiple projects using this plugin and they were all functioning properly. One project would connect 2 clients to a relay websocket server, and they would send their mouse positions to the relay server which relays it back to other connected clients, this worked fine with libwebsocket.

I then replaced libwebsocket with IWebSocket because I could not compile libwebsocket to the Xbox framework, because it wasn’t supported there. So I resorted to using IWebSocket assuming it should build there since it is a UE module.

My issue is that now there is a huge delay in messages sent at high frequency. My mouse position project was sending mouse position updates on Tick, every frame. While this was working with libwebsocket it is very delayed with IWebSocket.

I want to fix this in my plugin and not have to change anything in my projects specifically.

Right now I found that if I sent my mouse position updates on an interval of 0.1 seconds, there is no delay. So definitely some throttling might be required but this is requiring a change in my projects, I would rather make this change in the plugin itself, however I can’t implement this throttling mechanism there because I don’t want it to apply to every packet being sent, only the ones that are being sent every frame or consistently like position updates, but for some one-time event packets I wouldn’t want those to be affected.

I’m not sure how to get that solution working, any advice or tips would be greatly appreciated.

Adding some more details and code of what I’ve got going on:

WebSocketTest.h

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include <WebSockets/Public/IWebSocket.h>
#include <WebSockets/Public/IWebSocketsManager.h>
#include <WebSockets/Public/WebSocketsModule.h>
#include "WebSocketTest.generated.h"

DECLARE_DYNAMIC_DELEGATE_OneParam(FMessageCallbackDelegate, const FString&, OutputString);
DECLARE_DYNAMIC_DELEGATE_OneParam(FDataCallbackDelegate, const TArray<uint8>&, Data);

UCLASS()
class DEDICATEDDEMO_API AWebSocketTest : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AWebSocketTest();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UFUNCTION(BlueprintCallable, Category = "WebSocket Test")
	void ConnectSocket(const FString& url, FMessageCallbackDelegate OnMessageCallback, FDataCallbackDelegate OnDataCallback);

	UFUNCTION(BlueprintCallable, Category = "WebSocket Test")
	void SendMessage(const FString& message);

	UFUNCTION(BlueprintCallable, Category = "WebSocket Test")
	void SendData(const TArray<uint8>& data);

	TSharedPtr<IWebSocket> WebSocket;

};

WebSocketTest.cpp

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


#include "WebSocketTest.h"

// Sets default values
AWebSocketTest::AWebSocketTest()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AWebSocketTest::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AWebSocketTest::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AWebSocketTest::ConnectSocket(const FString& url, FMessageCallbackDelegate OnMessageCallback, FDataCallbackDelegate OnDataCallback)
{
	WebSocket = FWebSocketsModule::Get().CreateWebSocket(url);

	WebSocket->OnMessage().AddLambda([OnMessageCallback](const FString& message)
		{
			UE_LOG(LogTemp, Log, TEXT("[WebSocket] Message received: %s"), *message);
			OnMessageCallback.ExecuteIfBound(message);
		});

	WebSocket->OnRawMessage().AddLambda([OnDataCallback](const void* Data, SIZE_T Size, SIZE_T)
		{
			UE_LOG(LogTemp, Log, TEXT("[WebSocket] Data received"));
			TArray<uint8> DataArray;
			DataArray.Append((const uint8*)Data, Size);

			OnDataCallback.ExecuteIfBound(DataArray);
		});

	WebSocket->OnMessageSent().AddLambda([](const FString& message) 
		{
			UE_LOG(LogTemp, Log, TEXT("[WebSocket] Message sent %s"), *message);
		});

	WebSocket->OnConnected().AddLambda([]()
		{
			UE_LOG(LogTemp, Log, TEXT("[WebSocket] Connected"));
		});

	WebSocket->Connect();

	UE_LOG(LogTemp, Log, TEXT("[WebSocket] Connecting to URL %s"), *url);
}

void AWebSocketTest::SendMessage(const FString& message)
{
	if (WebSocket.IsValid() && WebSocket->IsConnected())
	{
		WebSocket->Send(message);
	}
}

void AWebSocketTest::SendData(const TArray<uint8>& data)
{
	if (WebSocket.IsValid() && WebSocket->IsConnected())
	{
		WebSocket->Send(data.GetData(), data.Num(), true);
	}
}


My Websocket tester actor blueprint that has WebSocketTest as base class.

I even tried this with a local websocket server, ws://localhost and I still see big delays in the messages being sent. Even after I stop playing the app I see messages continuing to be received in the console, why would this be happening even on localhost where there is no latency?

This is my localhost websocket server running on node

const WebSocket = require('ws');

// Create a WebSocket server
const server = new WebSocket.Server({ port: 8080 });

// Keep track of connected clients
const clients = new Set();

// Broadcast a message to all connected clients
function broadcast(message) {
  clients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

// Handle new client connections
server.on('connection', (client) => {
  // Add the new client to the set
  clients.add(client);

  // Handle incoming messages from the client
  client.on('message', (message) => {
    // Relay the message to all other connected clients
    broadcast(message);
  });

  // Handle client disconnection
  client.on('close', () => {
    // Remove the client from the set
    clients.delete(client);
  });
});

// Start the server
console.log('WebSocket relay server started on port 8080.');

It just relays messages to all connected clients.

Anyone should be able to test this and perhaps see the same results. How does libwebsocket do it though and why is this delay not observed with that?

Hello,

I am facing the same issue with IWebSocket.

Sending messages every frame (60 fps) result in a huge delay before the messages are actually sent on the network (I monitored it with WireShark), if they are even sent.

If I reduce the sending rate the delay gets shorter. At one message sent per second it works ok, at 30 per seconds I get around a 1 second delay. And it grows when I increase the message rate.

This makes it bearly usable for our realtime applications.

I’d love to know how to fix this also. :slight_smile:

1 Like