Glusoft

Create a TCP Client-Server Application with SDL3_net

TCP Client-Server Application with SDL3_net

The server

The goal for a server is to send a message when a client is connected.

The server should handle multiple users concurrently for that we will use a thread for each client.

Setup of the server

We start by defining some constant for the client and the buffer

#define MAX_CLIENTS 10
#define BUFFER_SIZE 512
#define SERVER_PORT 8654

The client struct

For the client we will need to store the socket, the thread and the id forr that we use a struct:

typedef struct {
    NET_StreamSocket* socket;
    SDL_Thread* thread;
    int id;
} Client;

Init SDL3_net and SDL3

SDL_Init(SDL_INIT_VIDEO);
NET_Init();

Resolve the hostname : Create the NET_Address

We resolve the hostname for running something localhost, we then wait at most 2 seconds for the adress to be resolved.

NET_Address* ip = NET_ResolveHostname("127.0.0.1");
NET_WaitUntilResolved(ip, 2000);

if (!ip) {
    printf("ResolveHost error: %s\n", SDL_GetError());
    return 1;
}

Create the NET_Server

NET_Server* server = NET_CreateServer(ip, SERVER_PORT);

if (!server) {
    printf("TCP_Open error: %s\n", SDL_GetError());
    return 1;
}

Create the server tcp socket

We create the tcp socket and wait for the acceptation of the client

NET_StreamSocket *stream;
bool new_client = NET_AcceptClient(server, &stream);
if (!new_client || !stream) {
    SDL_Delay(10);
    continue;
}

We need to wait for the connection we set a timeout of 2 seconds. If the client failed to connect we destroy the socket

if (!NET_WaitUntilConnected(stream, 2000)) {
    printf("Client failed to connect properly.\n");
    NET_DestroyStreamSocket(stream);
    continue;
}

The max number is reached

If the max number f client is reached we destroy the socket the server is full.

if (client_count >= MAX_CLIENTS) {
    printf("Max clients reached. Connection refused.\n");
    NET_DestroyStreamSocket(stream);
    continue;
}

Handling the client

We can fill the arrays of client with the informations we have and start the thread for the client:

clients[client_count].socket = stream;
clients[client_count].id = client_count;
clients[client_count].thread = SDL_CreateThread(client_thread_func, "ClientThread", &clients[client_count]);

After that we check if the thread is correctly created:

if (!clients[client_count].thread) {
    printf("Failed to create thread for client %d\n", client_count);
    NET_DestroyStreamSocket(stream);
} else {
    printf("[Client %d] Connected\n", client_count);
    client_count++;
}

The client thread

Inside the thread the server wait for a message with NET_ReadFromStreamSocket send back a message with NET_WriteToStreamSocket.

int client_thread_func(void* data) {
    Client* client = (Client*)data;
    char buffer[BUFFER_SIZE];

    printf("[Client %d] Thread started\n", client->id);

    while (true) {
        if(!client->socket) break;
        
        bool ready = NET_WaitUntilInputAvailable((void**)&client->socket, 1, 5000);
        if (!ready) {
            continue;
        }
        
        int len = NET_ReadFromStreamSocket(client->socket, buffer, BUFFER_SIZE - 1);
        
        if (len <= 0) {
            printf("[Client %d] Disconnected\n", client->id);
            break;
        }
        const char* message = "Hello from server!";
        len = (int)strlen(message);
        
        printf("[Client %d] Received: %s\n", client->id, buffer);

        // Echo back to client
        NET_WriteToStreamSocket(client->socket, message, len);
        NET_WaitUntilStreamSocketDrained(client->socket, 2000);
    }

    NET_DestroyStreamSocket(client->socket);
    return 0;
}

The cleanup

After exiting the loop you need to do some clean up.

NET_DestroyServer(server);
NET_Quit();
SDL_Quit();
return 0;

The client

The client send a message to the server after connection.

The defines

#define SERVER_PORT 8654
#define BUFFER_SIZE 512

Init SDL3_net and SDL3

SDL_Init(SDL_INIT_VIDEO);
NET_Init();

Create the NET_Address

NET_Address* server_addr = NET_ResolveHostname("127.0.0.1");
NET_WaitUntilResolved(server_addr, 2000);

if (!server_addr) {
    printf("Failed to resolve server address.\n");
    return 1;
}

Create the client tcp socket

We cerate the tcp socket and wait for the client to be connected to the server

NET_StreamSocket* socket = NET_CreateClient(server_addr, SERVER_PORT);
if (!socket) {
    printf("Failed to create stream socket.\n");
    return 1;
}

if (!NET_WaitUntilConnected(socket, 2000)) {
    printf("Connection timed out.\n");
    NET_DestroyStreamSocket(socket);
    return 1;
}

Send a message

We can send a message to the server and wait 2 seconds until everything is sent.

const char* message = "Hello from client!";
int len = (int)strlen(message);

printf("Sending: %s\n", message);
NET_WriteToStreamSocket(socket, message, len);
NET_WaitUntilStreamSocketDrained(socket, 2000);

We wait for the client to be available again.

bool ready = NET_WaitUntilInputAvailable((void**)&socket, 1, 5000);
while (!ready) {
    continue;
}

We read a message from the server and display the message.

char buffer[BUFFER_SIZE];
int received = NET_ReadFromStreamSocket(socket, buffer, BUFFER_SIZE - 1);

if (received > 0) {
    buffer[received] = '\0';
    printf("Received echo: %s\n", buffer);
} else {
    printf("No response or connection closed.\n");
}

Cleanup

After exiting the loop we need to destroy the tcp socket.

NET_DestroyStreamSocket(socket);
NET_Quit();
SDL_Quit();

Download the full project : Create a TCP Client-Server Application with SDL3_net

Need another OS ? => Windows, Mac, Linux