Distributed Programming  
Lecture 05 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 + "|";  
}
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 == 'c') {  
SessionInfo session;  
session.id = sessioncount++;  
session.name = params.at(0);  
session.serverip = "127.0.0.1"; //DEDICATED SERVER IP  
session.serverport = 7777;  
sessions.push_back(session);  
//INSTANTIATE DEDICATED SERVER HERE  
string message = "o|" + session.serverip + "|" +  
to_string(session.serverport) + "|";  
if (send(player.client, message.c_str(), message.length(), 0)  
== SOCKET_ERROR) {  
cout << "send() failed" << endl;  
}
}
Matchmaking Server  
else if (cmd == 'j') {  
int sID = stoi(params.at(0));  
bool joined = false;  
for (list<SessionInfo>::iterator it = sessions.begin();  
it != sessions.end(); it++) {  
if (it->id == sID){  
string message = "o|" + it->serverip + "|" +  
to_string(it->serverport) + "|";  
if (send(player.client, message.c_str(), message.length(), 0)  
== SOCKET_ERROR) {  
cout << "send() failed" << endl;  
}
joined = true;  
break;  
}
}
if (!joined){  
cout << "join failed" << endl;  
}
}
Matchmaking Server  
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 (Player State): MyPlayerState  
Unreal Engine Matchmaking  
Instantiate the interface and handle communication results:  
USTRUCT()  
MyPlayerState.h  
struct FSessionInfo {  
GENERATED_BODY()  
UPROPERTY()  
int id;  
UPROPERTY()  
FString name;  
UPROPERTY()  
FString serverip;  
UPROPERTY()  
int serverport;  
};  
UCLASS()  
class MYPROJECT10_API AMyPlayerState : public APlayerState{  
GENERATED_BODY()  
public:  
void UpdateSessionsList(FString serverinfo);  
void ConnectToGameServer(FSessionInfo session);  
Unreal Engine Matchmaking  
protected:  
virtual void BeginPlay() override;  
MyPlayerState.h  
class TCPClient* tcpClient;  
UPROPERTY(EditDefaultsOnly, Category = "UI")  
TSubclassOf<class UUserWidget> MatchmakingWidgetClass;  
class UUserWidget* MatchmakingWidget;  
class UScrollBox* serverListScrollBoxWidget;  
TArray<FSessionInfo*> *serversList;  
FTimerHandle serverListTimerHandle;  
bool canConnectToGameServer;  
FSessionInfo connectToGameServerSession;  
UFUNCTION()  
void OnNewSessionClicked();  
UFUNCTION()  
void OnUpdateServerList();  
Unreal Engine Matchmaking  
void AMyPlayerState::BeginPlay(){  
MyPlayerState.cpp  
FString levelName = GetWorld()->GetMapName();  
levelName.RemoveFromStart(GetWorld()->StreamingLevelsPrefix);  
if (levelName == "StartMap"){  
tcpClient = new TCPClient(this);  
canConnectToGameServer = false;  
if ((MatchmakingWidgetClass) && (tcpClient->IsConnected())) {  
serversList = new TArray<FSessionInfo*>();  
GetWorld()->GetTimerManager().SetTimer(serverListTimerHandle,  
this, &AMyPlayerState::OnUpdateServerList, 2, true);  
MatchmakingWidget = CreateWidget<UUserWidget>(GetWorld(),  
MatchmakingWidgetClass);  
MatchmakingWidget->AddToViewport();  
serverListScrollBoxWidget = Cast<UScrollBox>(  
MatchmakingWidget->GetWidgetFromName(TEXT("MyScrollBox")));  
UButton* newSessionButton = Cast<UButton>(  
MatchmakingWidget->GetWidgetFromName(TEXT("NewSession")));  
if (newSessionButton) {  
newSessionButton->SetIsEnabled(true);  
newSessionButton->OnClicked.AddDynamic(this,  
&AMyPlayerState::OnNewSessionClicked);  
}
}
} }  
Unreal Engine Matchmaking  
void AMyPlayerState::OnNewSessionClicked()  
{
MyPlayerState.cpp  
tcpClient->CreateNewGameSession("My test session");  
}
void AMyPlayerState::UpdateSessionsList(FString serverinfo){  
TArray<FString> Out;  
serverinfo.ParseIntoArray(Out, TEXT("|"), true);  
for (int i = 1; i < Out.Num() - 1; i += 2) {  
FSessionInfo *tempInfo = new FSessionInfo();  
tempInfo->id = FCString::Atoi(*Out[i]);  
tempInfo->name = Out[i + 1];  
tempInfo->serverip = "";  
tempInfo->serverport = -1;  
serversList->Add(tempInfo);  
}
}
void AMyPlayerState::ConnectToGameServer(FSessionInfo session){  
canConnectToGameServer = true;  
connectToGameServerSession = session;  
}
Unreal Engine Matchmaking  
void AMyPlayerState::OnUpdateServerList()  
{
MyPlayerState.cpp  
if (tcpClient) {  
if (tcpClient->IsConnected()) {  
if (serversList->Num() > 0) {  
if ((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]->id,  
tcpClient);  
UTextBlock* ItemWidgetText = NewObject<UTextBlock>();  
ItemWidgetText->SetText(FText::FromString(  
(
*serversList)[i]->name));  
ItemWidget->AddChild(ItemWidgetText);  
..  
.
Unreal Engine Matchmaking  
UVerticalBoxSlot* Slot = ItemWidgetsBox->  
AddChildToVerticalBox(ItemWidget);  
static FMargin Padding(5);  
Slot->SetPadding(Padding);  
}
}
}
if (canConnectToGameServer){  
APlayerController* pController = GetWorld()->  
GetFirstPlayerController();  
if (pController){  
FString cmd = "open " + connectToGameServerSession.serverip  
":" + FString::FromInt(connectToGameServerSession.serverport);  
tcpClient->Stop();  
+
canConnectToGameServer = false;  
MatchmakingWidget->RemoveFromViewport();  
pController->ConsoleCommand(cmd);  
}
}
}
}
}
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()  
public:  
UMyButton();  
void SetSessionInfo(int sID, TCPClient *tclient);  
protected:  
int sessionID;  
TCPClient *tcpClient;  
UFUNCTION()  
void OnClick();  
};  
Unreal Engine Matchmaking  
UMyButton::UMyButton()  
{
MyButton.cpp  
OnClicked.AddDynamic(this, &UMyButton::OnClick);  
}
void UMyButton::SetSessionInfo(int sID, TCPClient *tclient)  
{
sessionID = sID;  
tcpClient = tclient;  
}
void UMyButton::OnClick()  
{
tcpClient->JoinGameSession(sessionID);  
}
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 AMyPlayerState* pState);  
~TCPClient();  
virtual bool Init();  
virtual uint32 Run();  
virtual void Stop();  
void CreateNewGameSession(FString sname);  
void JoinGameSession(int sID);  
bool IsConnected();  
private:  
FRunnableThread* Thread;  
FSocket* Socket;  
FSocket* ListenerSocket;  
bool running;  
bool connected;  
class AMyPlayerState* PlayerState;  
};  
Unreal Engine Matchmaking  
TCPClient.cpp  
TCPClient::TCPClient(AMyPlayerState* pState) {  
Thread = FRunnableThread::Create(this, TEXT("TCPClientThread"), 0,  
TPri_Normal);  
PlayerState = pState;  
}
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') && (PlayerState)) {  
PlayerState->UpdateSessionsList(ServerMessage);  
}
else if ((ServerMessage[0] == 'o') && (PlayerState)) {  
TArray<FString> Out;  
ServerMessage.ParseIntoArray(Out, TEXT("|"), true);  
FSessionInfo session;  
session.id = -1;  
session.name = "";  
...  
Unreal Engine Matchmaking  
session.serverip = Out[1];  
session.serverport = FCString::Atoi(*Out[2]);  
PlayerState->ConnectToGameServer(session);  
TCPClient.cpp  
}
}
}
return 0;  
}
void TCPClient::CreateNewGameSession(FString sname) {  
FString serialized = "c|" + sname + "|#";  
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  
TCPClient.cpp  
void TCPClient::JoinGameSession(int sID)  
{
FString serialized = "j|" + FString::FromInt(sID) + "|#";  
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