Distributed Programming  
Lecture 06 Matchmaking Server in C++ on  
Unreal Engine  
Edirlei Soares de Lima  
<edirlei.lima@universidadeeuropeia.pt>  
What is a Matchmaking Server?  
In multiplayer games, matchmaking is the process of connecting  
players together for online play sessions.  
A matchmaking system allows players to:  
Create game sessions;  
Find and join game sessions;  
Many matchmaking systems feature a ranking system that  
attempts to match players of roughly equal ability together.  
Matchmaking with Dedicated Servers  
join  
instantiate  
play  
Matchmaking with Player as Server  
play  
join  
Game Project  
Session Data  
Players  
+
+
+
+
+
ID: int  
Name: string  
ServerIP: string  
ServerPort: string  
Player 1  
Player 2  
Player 3  
Matchmaking  
Server  
.
.
.
Matchmaking Server  
Create a new C++ empty project:  
Matchmaking Server  
Structures and variables:  
#define MAXPENDING 5  
#define RCVBUFSIZE 1024  
typedef struct playerinfo {  
SOCKET client;  
int id;  
}
PlayerInfo;  
typedef struct sessioninfo {  
int id;  
string name;  
string serverip;  
int serverport;  
}
SessionInfo;  
list<PlayerInfo> players;  
list<SessionInfo> sessions;  
int playercount = 0;  
int sessioncount = 0;  
Matchmaking Server  
Parsing and Interpreting client messages:  
void InterpretClientMessage(char *buffer, PlayerInfo player)  
{
string temp = "";  
vector<string> params;  
char cmd = 0;  
for (int i = 0; buffer[i] != '#'; i++){  
if ((buffer[i] == '|') && (cmd == 0)){  
cmd = temp[0];  
temp = "";  
}
else if ((buffer[i] == '|') && (cmd != 0)){  
params.push_back(temp);  
temp = "";  
}
else{  
temp = temp + buffer[i];  
}
}
...  
Matchmaking Server  
if (cmd == 'g'){  
string message = "s|";  
if (sessions.size() > 0){  
for (list<SessionInfo>::iterator it = sessions.begin();  
it != sessions.end(); it++){  
message = message + to_string(it->id) + "|" + it->name +  
"|" + it->serverip + "|" +  
to_string(it->serverport) + "|";  
if (send(player.client, message.c_str(), message.length(), 0)  
== SOCKET_ERROR){  
cout << "send() failed" << endl;  
}
}
}
else{  
message = message + "null|";  
if (send(player.client, message.c_str(), message.length(), 0)  
== SOCKET_ERROR){  
cout << "send() failed" << endl;  
}
}
}
Matchmaking Server  
else if (cmd == 'h'){  
SessionInfo session;  
session.id = sessioncount++;  
session.name = params.at(0);  
session.serverip = params.at(1);  
session.serverport = stoi(params.at(2));  
sessions.push_back(session);  
string message = "o|" + params.at(2) + "|";  
if (send(player.client, message.c_str(), message.length(), 0)  
== SOCKET_ERROR){  
cout << "send() failed" << endl;  
}
}
else{  
char *endline = strchr(buffer, '#’);  
buffer[(int)(endline - buffer) + 1] = 0;  
cout << "Unknown message: " << buffer << endl;  
}
}
Matchmaking Server  
Thread function to handle client connections:  
void HandleClientThread(PlayerInfo player)  
{
char buffer[RCVBUFSIZE];  
while (recv(player.client, buffer, sizeof(buffer), 0) > 0)  
{
InterpretClientMessage(buffer, player);  
memset(buffer, 0, sizeof(buffer));  
}
if (closesocket(player.client) == SOCKET_ERROR)  
{
cout << "closesocket() failed" << endl;  
}
}
Matchmaking Server  
Main function to initialize the server and wait for connections:  
int main(){  
SOCKET server;  
SOCKADDR_IN server_addr, client_addr;  
WSADATA wsaData;  
if (WSAStartup(MAKEWORD(2, 0), &wsaData) != NO_ERROR){  
cout << "WSAStartup() failed" << endl;  
exit(EXIT_FAILURE);  
}
if ((server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))  
== INVALID_SOCKET){  
cout << "socket() failed" << endl;  
exit(EXIT_FAILURE);  
}
memset(&server_addr, 0, sizeof(server_addr));  
server_addr.sin_family = AF_INET;  
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
server_addr.sin_port = htons(8856);  
Matchmaking Server  
if (::bind(server, (struct sockaddr *) &server_addr,  
sizeof(server_addr)) == SOCKET_ERROR){  
cout << "bind() failed" << endl;  
exit(EXIT_FAILURE);  
}
if (listen(server, MAXPENDING) == SOCKET_ERROR){  
cout << "listen() failed" << endl;  
exit(EXIT_FAILURE);  
}
cout << "Server Started!" << endl;  
while (true){  
SOCKET client;  
int clientlen = sizeof(client_addr);  
if ((client = accept(server, (struct sockaddr *) &client_addr,  
&clientlen)) == INVALID_SOCKET){  
cout << "accept() failed" << endl;  
}
Matchmaking Server  
char addrstr[INET_ADDRSTRLEN];  
inet_ntop(AF_INET, &(client_addr.sin_addr), addrstr,  
INET_ADDRSTRLEN);  
cout << "Connection from " << addrstr << endl;  
PlayerInfo cinfo;  
cinfo.client = client;  
cinfo.id = playercount++;  
players.push_back(cinfo);  
thread *clientthread = new std::thread(HandleClientThread,  
cinfo);  
}
}
}
Unreal Engine Matchmaking  
Create a new widget blueprint for the matchmaking interface:  
Network Communication in Unreal Engine  
Project dependencies to be added to the project build file  
(YourGameName.Build.cs):  
Sockets;  
Networking;  
UMG;  
...  
PublicDependencyModuleNames.AddRange(new string[] { "Core",  
"
"
CoreUObject", "Engine", "InputCore", "AIModule",  
Sockets", "Networking", "UMG" });  
...  
Unreal Engine Matchmaking  
Create a new C++ class (LevelScriptActor): MyLevelScriptActor  
Unreal Engine Matchmaking  
Instantiate the interface and handle communication results:  
USTRUCT()  
MyLevelScriptActor.h  
struct FSessionInfo {  
GENERATED_BODY()  
UPROPERTY()  
int id;  
UPROPERTY()  
FString name;  
UPROPERTY()  
FString serverip;  
UPROPERTY()  
int serverport;  
};  
UCLASS()  
class MYGAMEMULTIPLAYER_API AMyLevelScriptActor : public  
ALevelScriptActor{  
GENERATED_BODY()  
public:  
AMyLevelScriptActor();  
void UpdateSessionsList(FString serverinfo);  
void StartGameHost(int port);  
Unreal Engine Matchmaking  
protected:  
virtual void BeginPlay() override;  
MyLevelScriptActor.h  
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason)  
override;  
virtual void Tick(float DeltaTime) override;  
bool readyToHost;  
bool hostPort;  
TCPClient* tcpclient;  
TArray<FSessionInfo*> *serversList;  
UPROPERTY(EditDefaultsOnly, Category = "UI")  
TSubclassOf<class UUserWidget> MatchmakingWidgetClass;  
UUserWidget* MatchmakingWidget;  
UScrollBox* serverListScrollBoxWidget;  
FTimerHandle serverListTimerHandle;  
UFUNCTION()  
void OnUpdateServerList();  
UFUNCTION()  
void OnConnectClicked();  
UFUNCTION()  
void OnHostClicked();  
Unreal Engine Matchmaking  
void AMyLevelScriptActor::BeginPlay(){  
Super::BeginPlay();  
MyLevelScriptActor.cpp  
readyToHost = false;  
serversList = new TArray<FSessionInfo*>();  
GetWorld()->GetTimerManager().SetTimer(serverListTimerHandle,  
this, &AMyLevelScriptActor::OnUpdateServerList, 2, true);  
if (MatchmakingWidgetClass) {  
MatchmakingWidget = CreateWidget<UUserWidget>(GetWorld(),  
MatchmakingWidgetClass);  
MatchmakingWidget->AddToViewport();  
UButton* connectButton = Cast<UButton>(MatchmakingWidget  
-
>GetWidgetFromName(TEXT("ConnectButton")));  
if (connectButton) {  
connectButton->OnClicked.AddDynamic(this,  
AMyLevelScriptActor::OnConnectClicked);  
&
}
UButton* hostButton = Cast<UButton>(MatchmakingWidget  
>GetWidgetFromName(TEXT("HostButton")));  
-
if (hostButton) {  
hostButton->SetIsEnabled(false);  
hostButton->OnClicked.AddDynamic(this,  
&AMyLevelScriptActor::OnHostClicked);  
}
Unreal Engine Matchmaking  
..  
.
MyLevelScriptActor.cpp  
serverListScrollBoxWidget = Cast<UScrollBox>(MatchmakingWidget  
->GetWidgetFromName(TEXT("MyScrollBox")));  
}
APlayerController* pController = GetWorld()  
>GetFirstPlayerController();  
-
if (pController)  
{
pController->bShowMouseCursor = true;  
pController->bEnableClickEvents = true;  
pController->bEnableMouseOverEvents = true;  
}
void AMyLevelScriptActor::StartGameHost(int port)  
{
hostPort = port;  
readyToHost = true;  
}
Unreal Engine Matchmaking  
MyLevelScriptActor.cpp  
void AMyLevelScriptActor::UpdateSessionsList(FString serverinfo)  
{
TArray<FString> Out;  
serverinfo.ParseIntoArray(Out, TEXT("|"), true);  
for (int i = 1; i < Out.Num() - 3; i+=4){  
FSessionInfo *tempInfo = new FSessionInfo();  
tempInfo->id = FCString::Atoi(*Out[i]);  
tempInfo->name = Out[i+1];  
tempInfo->serverip = Out[i+2];  
tempInfo->serverport = FCString::Atoi(*Out[i + 3]);  
serversList->Add(tempInfo);  
}
}
void AMyLevelScriptActor::OnConnectClicked()  
{
tcpclient = new TCPClient(this);  
}
Unreal Engine Matchmaking  
MyLevelScriptActor.cpp  
void AMyLevelScriptActor::OnHostClicked()  
{
tcpclient->HostNewGame("My test server", "7777");  
}
void AMyLevelScriptActor::OnUpdateServerList()  
{
if (tcpclient){  
if (tcpclient->IsConnected()){  
UButton* hostButton = Cast<UButton>(MatchmakingWidget  
->GetWidgetFromName(TEXT("HostButton")));  
hostButton->SetIsEnabled(true);  
if (readyToHost){  
if (APlayerController* pController = GetWorld()  
->GetFirstPlayerController()){  
pController->ConsoleCommand("open MyMap?listen");  
}
}
}
Unreal Engine Matchmaking  
MyLevelScriptActor.cpp  
if (serversList->Num() > 0){  
if ((MatchmakingWidgetClass) && (MatchmakingWidget) &&  
(
serverListScrollBoxWidget)){  
TArray<UWidget*> allChildren = serverListScrollBoxWidget  
>GetAllChildren();  
-
for (int i = 0; i < allChildren.Num(); i++){  
allChildren[i]->RemoveFromParent();  
}
for (int i = 0; i < serversList->Num(); i++){  
UVerticalBox* ItemWidgetsBox = NewObject<UVerticalBox>();  
serverListScrollBoxWidget->AddChild(ItemWidgetsBox);  
UMyButton* ItemWidget = NewObject<UMyButton>(this);  
ItemWidget->SetSessionInfo((*serversList)[i]);  
UTextBlock* ItemWidgetText = NewObject<UTextBlock>();  
ItemWidgetText->SetText(FText::FromString((*serversList)[i]  
->name));  
ItemWidget->AddChild(ItemWidgetText);  
UVerticalBoxSlot* Slot = ItemWidgetsBox  
->AddChildToVerticalBox(ItemWidget);  
static FMargin Padding(5);  
Slot->SetPadding(Padding);  
}
}
} } }  
Unreal Engine Matchmaking  
Change the level class in the project settings:  
Unreal Engine Matchmaking  
Create a new C++ Button class: MyButton  
Unreal Engine Matchmaking  
The customized button must know his session information in  
order to connect when clicked:  
UCLASS()  
class MYGAMEMULTIPLAYER_API UMyButton : public UButton  
MyButton.h  
{
GENERATED_BODY()  
UMyButton();  
struct FSessionInfo *sessionInfo;  
UFUNCTION()  
void OnClick();  
public :  
void SetSessionInfo(struct FSessionInfo *sInfo);  
};  
Unreal Engine Matchmaking  
UMyButton::UMyButton()  
{
MyButton.cpp  
OnClicked.AddDynamic(this, &UMyButton::OnClick);  
}
void UMyButton::OnClick()  
{
if (GetOuter()->GetWorld()) {  
if (APlayerController* pController = GetOuter()->GetWorld()->  
GetFirstPlayerController()){  
FString cmd = "open " + this->sessionInfo->serverip + ":" +  
FString::FromInt(this->sessionInfo->serverport);  
pController->ConsoleCommand(cmd);  
}
}
}
void UMyButton::SetSessionInfo(FSessionInfo *sInfo)  
{
sessionInfo = sInfo;  
}
Unreal Engine Matchmaking  
Create a new C++ class: TCPClient  
Unreal Engine Matchmaking  
Connect with the server, send and receive network messages:  
class MYGAMEMULTIPLAYER_API TCPClient : public FRunnable{  
TCPClient.h  
public:  
TCPClient(class AMyLevelScriptActor* gLevel);  
~TCPClient();  
virtual bool Init();  
virtual uint32 Run();  
virtual void Stop();  
void HostNewGame(FString sname, FString sport);  
bool IsConnected();  
private:  
FRunnableThread* Thread;  
FSocket* Socket;  
FSocket* ListenerSocket;  
bool running;  
bool connected;  
class AMyLevelScriptActor* gameLevel;  
};  
Unreal Engine Matchmaking  
TCPClient.cpp  
TCPClient::TCPClient(AMyLevelScriptActor* gLevel){  
Thread = FRunnableThread::Create(this, TEXT("TCPClientThread"), 0,  
TPri_Normal);  
gameLevel = gLevel;  
}
TCPClient::~TCPClient(){  
Stop();  
delete Thread;  
}
void TCPClient::Stop(){  
running = false;  
}
bool TCPClient::IsConnected(){  
return connected;  
}
Unreal Engine Matchmaking  
TCPClient.cpp  
bool TCPClient::Init()  
{
Socket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)  
>CreateSocket(NAME_Stream, TEXT("default"), false);  
-
int32 NewSize = 0;  
Socket->SetReceiveBufferSize(1024, NewSize);  
FIPv4Address matchmakingServerIP(127, 0, 0, 1);  
TSharedRef<FInternetAddr> matchmakingServer = ISocketSubsystem::  
Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();  
matchmakingServer->SetIp(matchmakingServerIP.Value);  
matchmakingServer->SetPort(8856);  
connected = Socket->Connect(*matchmakingServer);  
if (connected)  
{
UE_LOG(LogTemp, Log, TEXT("CONNECTED!"));  
FString serialized = TEXT("g|#");  
TCHAR *serializedChar = serialized.GetCharArray().GetData();  
int32 size = FCString::Strlen(serializedChar);  
int32 sent = 0;  
...  
Unreal Engine Matchmaking  
..  
.
TCPClient.cpp  
bool successful = Socket->Send((uint8*)TCHAR_TO_UTF8(  
serializedChar), size, sent);  
if (successful)  
{
UE_LOG(LogTemp, Log, TEXT("MESSAGE SENT!"));  
return true;  
}
else{  
UE_LOG(LogTemp, Log, TEXT("ERROR: NOT CONNECTED!"));  
return false;  
}
}
Unreal Engine Matchmaking  
uint32 TCPClient::Run(){  
running = true;  
TCPClient.cpp  
TArray<uint8> ReceivedData;  
while (running){  
uint32 Size = 0;  
if (Socket->HasPendingData(Size)){  
ReceivedData.Init(0, Size);  
int32 Read = 0;  
Socket->Recv(ReceivedData.GetData(), ReceivedData.Num(), Read);  
FString ServerMessage = FString(UTF8_TO_TCHAR(  
ReceivedData.GetData()));  
UE_LOG(LogTemp, Log, TEXT("RECEIVED: %s"), *ServerMessage);  
if ((ServerMessage[0] == 's') && (gameLevel)){  
gameLevel->UpdateSessionsList(ServerMessage);  
}
else if ((ServerMessage[0] == 'o') && (gameLevel)){  
TArray<FString> Out;  
ServerMessage.ParseIntoArray(Out, TEXT("|"), true);  
gameLevel->StartGameHost(FCString::Atoi(*Out[1]));  
}
} }  
return 0;  
}
Unreal Engine Matchmaking  
TCPClient.cpp  
void TCPClient::HostNewGame(FString sname, FString sport){  
bool canBind = false;  
TSharedRef<FInternetAddr> localIp = ISocketSubsystem::Get(  
PLATFORM_SOCKETSUBSYSTEM)->GetLocalHostAddr(*GLog, canBind);  
if (localIp->IsValid()){  
FString serialized = "h|" + sname + "|" + localIp->  
ToString(false) + "|" + sport + "|#";  
TCHAR *serializedChar = serialized.GetCharArray().GetData();  
int32 size = FCString::Strlen(serializedChar);  
int32 sent = 0;  
bool successful = Socket->Send((uint8*)TCHAR_TO_UTF8(  
serializedChar), size, sent);  
if (successful)  
UE_LOG(LogTemp, Log, TEXT("MESSAGE SENT!!!!"));  
}
}
Unreal Engine Matchmaking  
Further Reading  
Carnall, B. (2016). Unreal Engine 4.X By Example. Packt  
Publishing. ISBN: 978-1785885532.  
Web Resources:  
Third Party Socket Server Connection -  
https://wiki.unrealengine.com/Third_Party_Socket_Server_Connection  
TCP Socket Listener, Receive Binary Data From an IP/Port Into UE4 -  
https://wiki.unrealengine.com/TCP_Socket_Listener,_Receive_Binary_D  
ata_From_an_IP/Port_Into_UE4,_%28Full_Code_Sample%29