[Plugin] Socket.io Client

Interesting that sounds like the timeout happens on the game thread, the solution is to simply make the connection attempt off-thread. I’ve added it to issues: https://github.com//socketio-client-ue4/issues/3 and will try to address it next time I make a pass on the plugin. Feel free to make pull requests if you fix it before :slight_smile:

Hi,
thanks for the plugin. It seems I have some trouble getting it to work though. I use the version for UE4.11, socketIO is running on 0.9. I try to connect to socket io with connect node “http://192.168.1.50:5000” then I want to emit a command. The colleague who set up the Server said SocketIO Namespace was “events” and the event I need to send to is called “unityFanSpeedEvent” and receives only a number between 0 and 100.

I guess the problem is the SocketIO namespace, I’m not sure in what node I need to add the namespace (connect or emit?). Sorry if this is some kind of a stupid question, it’s my first time working with socketIO.

Here is what I’m trying to do:
ue4SocketIO.PNG

So the current plugin is very bare bones, in terms of actual binding meat it’s only 40-50 lines of code, enough to have been done in a short presentation as a proof of concept:


#include "SocketIOClientPrivatePCH.h"
#include "SocketIOClientComponent.h"


USocketIOClientComponent::USocketIOClientComponent(const FObjectInitializer &init) : UActorComponent(init)
{
	bWantsInitializeComponent = true;
	bAutoActivate = true;
}

std::string StdString(FString UEString)
{
	return std::string(TCHAR_TO_UTF8(*UEString));
}
FString FStringFromStd(std::string StdString)
{
	return FString(StdString.c_str());
}


void USocketIOClientComponent::Connect(FString AddressAndPort)
{
	if (!AddressAndPort.IsEmpty())
	{
		PrivateClient.connect(StdString(AddressAndPort));
	}
	else
	{
		PrivateClient.connect("http://localhost:3000");
	}
}

void USocketIOClientComponent::Emit(FString Name, FString Data)
{
	PrivateClient.socket()->emit(StdString(Name), StdString(Data));
	//UE_LOG(LogTemp, Log, TEXT("Emit %s with %s"), *Name, *Data);
}

void USocketIOClientComponent::Bind(FString Name)
{
	PrivateClient.socket()->on(StdString(Name), sio::socket::event_listener_aux(&](std::string const& name, sio::message::ptr const& data, bool isAck, sio::message::list &ack_resp) {

		const FString SafeName = FStringFromStd(name);
		const FString SafeData = FStringFromStd(data->get_string());
		FFunctionGraphTask::CreateAndDispatchWhenReady(&, SafeName, SafeData]
		{
			On.Broadcast(SafeName, SafeData);
		}, TStatId(), nullptr, ENamedThreads::GameThread);
	}));
}

That is literally it. But it’s apparently useful, so I may have to look into extending it a bit :slight_smile:

It should be a fairly easy modification to support namespaces. The current code uses


PrivateClient.socket()-> ...

which fetches the default namespace “/” and the method should just expand to add a namespace variable e.g.



FString Namespace = FString(TEXT("events"));
PrivateClient.socket(StdString(Namespace))-> ... 


events.

Feel free to modify the source and make a pull request, otherwise it may take a bit of time before I make a pass on the plugin with the fixes. I’ve added a note about it in github issues on the repo.

Hi,
thanks for the quick reply, I will try that and let you know the results.

EDIT: I just tested my theory that it is an socketIO version problem since the legacy server is running 0.9. Set up some local servers on my pc one with 1.x and one for 0.9, 0.9 only throws warnings when Unreal tries to connect, 1.x accepts the connection.

Hi,
so I tried modifying your source code, but it seems I can’t get it to run. Even if I hardcode IP, namespace, eventname and data into the source nothing gets through to the server.



void USocketIOClientComponent::Connect(FString AddressAndPort, FString nSpace)
{
	if (!AddressAndPort.IsEmpty())
	{
		Namespace = nSpace;
		PrivateClient.connect("http://192.168.1.50:5000");//StdString(AddressAndPort));
		UE_LOG(LogTemp, Log, TEXT("%s"), *nSpace);
		
	}
...

void USocketIOClientComponent::Emit(FString Name, FString Data)
{
	FString speed = FString(TEXT("100"));
	FString nsp = FString(TEXT("/events"));
	FString eve = FString(TEXT("unityFanSpeedEvent"));
	PrivateClient.socket(StdString(nsp))->emit(StdString(eve),StdString(speed));
	//PrivateClient.socket("/servo"/*StdString(Namespace)*/)->emit("enable");//StdString(Name), StdString(Data));
	UE_LOG(LogTemp, Log, TEXT("Emit %s with %s"), *Name, *Data);
}


i also tried “events” or “events/” to make sure its not a problem with the namespace. Here is a code snippet from the Unity Project the Server was originally created for (written in C#):
Connect:



client = new Client("http://192.168.1.50:5000");

		client.Error += SocketError;
		client.Message += SocketMessage;

		socket = client.Connect ("/events");


Emit:



public void EventFanSpeed(int speed) {
		if (oldSpeed == speed)
			return;

		socket.Emit("unityFanSpeedEvent", "" + speed, null);
		oldSpeed = speed;
	}


Could it be that the Problem is that the server runs SocketIO 0.9?

Update to v0.2.0 for 4.13
-Added connect/disconnect signatures (these have to be implemented by
the server if used)
-Added C++ lambda binds do e.g.
BindDataLambdaToEvent(&](const FString& EventName, const FString& EventData) { //your code }, Name, Namespace);
to use it
-Bind changed name to BindEvent
-Added Namespace support
-Connection runs on background thread (should not be blocking anymore)

Grab it at the github repo release
https://github.com//socketio-client-ue4/releases/tag/0.2.0

Try the newest release and let me know if it works. That said I believe the c++ client is using a 1.x version so there may be compatibility issues with a 0.x socket.io server.

Hello , amazing plugin, I am just wondering is it possible to do all this socket.io bindings using code instead of BP?

Update to v0.2.1 for 4.13
-added proper binary send/receive support
-added functioning onconnect/disconnect events, no longer require
special events on your server
-added on namespace connect/disconnect events
-added on fail event

Grab it at the github repo releases url
Code changes found in this merged commit

Absolutely, I’m using this plugin in C++ for my own projects. Don’t have a C++ how to atm, but here’s a quick rundown:

To use the C++ code from the plugin add it as a dependency module in your project build.cs


PublicDependencyModuleNames.AddRange(new string] { "Core", "CoreUObject", "Engine", "InputCore", "SocketIOClient" });

Add the component to your actor or reference it from another component by getting it on begin play e.g.



SIOComponent = Cast<USocketIOClientComponent>(this->GetOwner()->GetComponentByClass(USocketIOClientComponent::StaticClass()));
if (!SIOComponent)
{
	UE_LOG(LogTemp, Warning, TEXT("No sister socket IO component found"));
	return;
}
else
{
	UE_LOG(LogTemp, Log, TEXT("Found SIOComponent: %s"), *SIOComponent->GetDesc());
}


Connect:



//get a reference or add as subobject in your actor
USocketIOClientComponent* SIOComponent;

//the component will autoconnect, but you may wish to change the url before it does that via
SIOComponent->AddressAndPort = FString("http://127.0.0.1:3000"); //change your address

//you can also disable auto connect and connect it at your own time via
SIOComponent->ShouldAutoConnect = false;
SIOComponent->Connect(); // or SIOComponent->Connect("http://127.0.0.1:3000");


String emit:



SIOComponent->Emit(FString(TEXT("myevent")), FString(TEXT("some data or stringified json"));


note that namespaces are supported as a third optional FString parameter.

Raw Data emit:



TArray<uint8> Buffer;

//fill buffer with your data

SIOComponent->EmitBuffer(FString(TEXT("myBinarySendEvent")), Buffer.GetData(), Buffer.Num());


to receive events you can bind lambdas which makes things awesomely easy e.g.



SIOComponent->BindDataLambdaToEvent(&](const FString& Name, const FString& Data)
		{
			//do something with your string data
		}, FString(TEXT("myStringReceiveEvent")));


Raw data receive:



SIOComponent->BindBinaryMessageLambdaToEvent(&](const FString& Name, const TArray<uint8>& Buffer)
		{
			//Do something with your buffer
		}, FString(TEXT("myBinaryReceiveEvent")));


or if you want to deal with raw socket.io message data (this plugin doesn’t have automatic UE json support yet)



SIOComponent->BindRawMessageLambdaToEvent(&](const FString& Name, const sio::message::ptr&)
		{
			//do something with your sio::message::ptr data 
		}, FString(TEXT("myArbitraryReceiveEvent")));


see https://github.com/socketio/socket.io-client-cpp/blob/master/src/sio_message.h for details on how to deal with raw sio messages.

I’ll hopefully consolidate the lambda types later on to handle UEJson format instead which will allow you to skip the stringification step on both sides if you want to build json objects. TBC!

Wow amazing response! I 'm your fan now! Thanks a lot, I am just wondering about one thing, as I know socket.io is TCP based will it work on multiplayer games from 500-5000 players? Will it be as fast as UDP?

Thanks!

Edit: forgot to mention TCP vs UDP

Since Socket.io is based on websockets it can only use TCP. This can be a problem, see here this article: UDP vs. TCP | Gaffer On Games counterpoints: TCP is very widely used by multiplayer gaming today. It turns out you can just t... | Hacker News

In modern networks you don’t get as many packet losses as you used to in the past. That said TCP performs worse than UDP for real-time loss tolerant applications because of its in-order setup. As a consequence you will receive old data, even if you have new data that can overwrite it which adds additional latency when you have packet losses. This means that your worst case scenarios will be worse than when using UDP. E.g. in a bad/unreliable connection your characters may appear frozen, and then unfreeze and do all their past movement really quickly vs just teleport to their latest position.

In general I personally don’t use socket.io for small real-time data e.g. character movement, for that I use UE4’s network replication system.
What I tend to use socket.io for is big data transfer (e.g. files) or logic events which aren’t <1 second time sensitive (new file event, chat, data sharing events). But that doesn’t mean it can’t be used for that, WoW was notoriously based entirely on TCP!

It’s definitely easier to write logic in node.js than messing with UE4’s dedicated server compilation and the mental gymnastics you have to do to understand the network replication system. It’s a question of trade offs, when everything works with no packet losses, both of the solutions will look the same to the end user.

In terms of overall throughput and performance, for that I point you to (~4 year old benchmark):
http://drewww.github.io/socket.io-benchmarking/

key takeaway:

Also

My personal test of around ~ 300 chat windows spawned from the example project suggests its stable for chat traffic for that size and I’ve seen chats up to ~2k work well.

That said, multiplayer traffic as a problem is an exponential function. To get high concurrency you need to cordon off sections of your traffic that don’t interact to keep things smooth. Generally this means that if you can’t see a user or they are far away, the server shouldn’t emit that traffic to you. You can experiment with socket.io rooms/namespaces when you try scaling which will effectively cordon users off to their respective sub-area and you can always add more hardware scaling by having those rooms on different servers. In general though I don’t think you would run into a problem until you’re transmitting a lot of data or have hundreds of users even on a single server machine.

So try to break it and then when you do finally hit the scaling problem, then optimize. Early optimizations usually just waste time as often your expectations != the actual situation.

Unrelated to socket.io, but here’s someone holding 1 Million long-poll connections active on a single node.js server (~16GB ram). It all depends on what you’re doing, ram, connection speed, and overall expected throughput from each client.

Update to 0.2.5
-Change event names and signatures
-Add wider support for raw message emitting and receiving

Apart from adding the missing raw methods for C++ for callbacks and raw message types, I’ve added a little bit more about how to use the plugin in C++. I expect this to get a bit simpler as I get auto-json conversion working at some point. Grab the latest source at the repo https://github.com//socketio-client-ue4

Update to 0.4.0
-Added SIOJson for Blueprint Json construction and conversion, based on VaRest
-Auto-conversion for nodes
-Blueprint callbacks and events bound to functions references by function name
-Conversion of JsonObjects to and from UStructs, including Blueprint UStructs with automatically trimmed long names into readable short names
-Overloaded EmitNative for all expect UE native data types
-Much simplified C++ usage based on FJsonValue and FJsonObject including nested binary support (hackery!)
-and much more

Grab it at the usual: https://github.com//socketio-client-ue4/releases

Seeing as I will probably use this plugin in production, decided to fix all major workflow issues with the plugin, it’s accumulated so many changes for this update, it jumped two major versions internally! Check out the github readme for the latest examples and documentation!

Some samples of the changes:

Blueprint construction, thanks in large parts to SIOJson forked from ufna’s VaRest


Any unreal struct you make, including blueprint ones are supported for easy conversion to json!


Binary and decode your complex nested data structures straight in blueprint


Good workflow from auto-conversion for native types


Callbacks, binding events to functions in your blueprints and encode/decode json straight from blueprints!


Much improved C++ workflow based on native FJsonValue and FJsonObject. Support for nested raw binary data within these structures!



TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);                        //make object option2
JsonObject->SetBoolField(FString("myBool"), false);
JsonObject->SetStringField(FString("myString"), FString("Socket.io is easy"));
JsonObject->SetNumberField(FString("myNumber"), 9001);

JsonObject->SetField(FString("myBinary1"), USIOJConvert::ToJsonValue(Buffer));              //binary option1 - shorthand
JsonObject->SetField(FString("myBinary2"), MakeShareable(new FJsonValueBinary(Buffer)));    //binary option2

JsonObject->SetArrayField(FString("myArray"), ArrayValue);
JsonObject->SetObjectField(FString("myNestedObject"), SmallObject);

SIOClientComponent->EmitNative(FString("nativeTest"), JsonObject);

Any UStruct to Json and easy c++ callbacks


//Set your struct variables
FTestCppStruct TestStruct;
TestStruct.Name = FString("George");
TestStruct.Index = 5;
TestStruct.SomeNumber = 5.123f;

SIOClientComponent->EmitNative(FString("callbackTest"),  FTestCppStruct::StaticStruct(), &TestStruct, &](auto Response)
{
    auto Message = Response[0]->AsObject();

    //Show what we received
    UE_LOG(LogTemp, Log, TEXT("Received a response: %s"), *USIOJConvert::ToJsonString(Message));

    //Set our second struct to the new values
    USIOJConvert::JsonObjectToUStruct(Message, FTestCppStruct::StaticStruct(), &MemberStruct);

    //Show that struct
    UE_LOG(LogTemp, Log, TEXT("Our received member name is now: %s"), *MemberStruct.Name);
});

Just wanted to ping you, if you still want to use this plugin give the latest version a try, it can now handle all Json types straight in blueprints :slight_smile:

Hi ,

I saw you and we chatted at UE4London last week (i’m the gameshow guy) - thanks for another amazing demo! Afterwards we were talking about socket.io and all the cool stuff you’ve been doing with it so I thought i’d have a go at implementing it in my project (The gameshow).

I’m quite interested in the auto-populating-struct from JSON functions, and i’ve had it working on a few tests. Is it possible to populate a struct containing an array of another type of struct? In my case i’ve got a QUESTION object (array of) which contains strings and some ints, but it also contains another array containing up to 4 ANSWER structs.

I’ve tried to cobble this together (I’m populating it from outside, rather than converting the struct and sending it out of the engine) but I get an engine crash when I send in the data.

Because of a weird issue I couldn’t actually get my node server to communicate with the socket.io plugin you made - i’m just using the conversion nodes to convert stringified JSON to the struct - I don’t know if that makes a difference.

Dan.

Great to hear from you Dan!

The key with JSON <-> struct conversion is that the keys have to match for the structure to fill properly.

In both C++ and BP structs, UE4 follows PascalCase whereas JSON generally expects camelCase. UE4 json conversion automatically converts struct properties into camelCase, so this is the format your data should hold.

E.g.:


the vector struct will convert into


{
  "x": 1,
  "y": -22.3,
  "z": 9001.5
}

(note the lower case properties)

So the first thing to check is to make sure all your json values use camelCase.

Regarding struct of structs, in the tensorflow-ue4-mnist-example (final demo) I used a similar structure, I had a BP struct called DrawingStruct, which contained an array of StrokeStruct, these are then populated via json->struct conversion when the webclient sends strokes. So that method should work.

Note that the json format for that example is something like:



{  
   "strokes":  
      {  
         "stroke":  
            {  
               "x":3.9862472895741048,
               "y":4.7506390606304745
            },
            {  
               "x":4.0216255688082621,
               "y":4.7864079379896598
            }
         ]
      },
      {  
         "stroke":  
            {  
               "x":20.610493174572404,
               "y":4.2582182499755357
            },
            {  
               "x":20.570929353931788,
               "y":4.2949240704982428
            }
         ]
      }
   ]
}



This was essentially a workaround for an array of arrays in BPs.

Regarding node.js communication, make sure you’re using socket.io version 1.0.

If those tips don’t solve it, PM me your logs that you get from the crash and if you can post any BP graphs showing how it’s linked up, maybe we can figure out what’s the issue :slight_smile:

Excited about your recent post @ #tensorflowUE4, just saw it today - thanks !!
https://github.com//tensorflow-ue4-mnist-example

It needs a bit of work before it is drag and drop ready. Atm you need to install pip and tensorflow separately, but I’m making progress on encapsulating pip dependencies and python scripts into plugins themselves. That should in theory also allow other future python -> bp plugins to be built with drag and drop installation. Stay tuned :wink:

Hi @ - thanks for the tip on socket.io v1 - I expect it’s that.

I realised (I’m a java newbie) that on my node server and in the SQLite database I’m using, I was stringifying the answer’s struct - so my json contained the normal strings and ints, but the answers were just an array of strings rather than valid json- it’s been a whirlwind of learning this last 2 weeks!

Cheers again though - I can see socket.io playing a huge part in my future projects. I might make a video tutorial like I did for Monsieur gustav’s ue4osc plugin if I get a spare moment - what do you think? I was really interested when you first did your presentation on this but had absolutely no idea about how to set up a node server - I could do a really simple introduction to both, making a web interface into the engine.

Awesome to see good progress, learning something new is always challenging but worth it when it all clicks together. A video tutorial would be a great addition, looking forward to seeing what you come up with :slight_smile: !

Hi @, nice work btw.

My UE4Editor is crashing as soon as I am connecting to my server. I should say that I am not using an socket.io server. With javascript in a browser everything works fine (communication with a sps-controller). I am using version 4.14.3 of unreal.
Do you have any ideas? I can’t even debug because the whole Unreal crashes, only with a short info “not working any more…” It crashes both on blueprint and on c++. Tried also the latest release(0.4.1) and my own compilation with the latest source files from github (0.4.7).

Thanks,