Open In App

How to create a multi-chat server using UDP?

In this article, we will see the development of a Multi-Threaded UDP Chat Server-Client Network for Data Transfer in Linux. UDP is used for low latency and connectionless characteristics, the architecture consists of a server managing multiple clients through threading. This networked chat application facilitates real-time communication, allowing users to exchange messages seamlessly. We will create the Server and Client script and perform live execution of communication between the server and multiple clients.

Creating Server Script

In this section, we will create the server script in C language. So follow the steps and break down the script to understand the implementation of the server.



Step 1: Create a server.c File

Firstly, create the server.c file in the nano editor by using the command in the terminal.

nano server.c

Creating server.c file

Step 2: Server Initialization and Socket Binding




int serverSocket;
struct sockaddr_in serverAddr;
char buffer[MAX_MESSAGE_SIZE];
socklen_t clientLen = sizeof(struct sockaddr_in);
ssize_t bytesRead;
 
// Create socket
if ((serverSocket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
    perror("Socket creation failed");
    exit(EXIT_FAILURE);
}
 
// Configure server address
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(PORT);
 
// Bind socket
if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
    perror("Bind failed");
    exit(EXIT_FAILURE);
}
 
// Initialize mutex
pthread_mutex_init(&mutex, NULL);
 
printf("Server listening on port %d...\n", PORT);

Step 3: Handling Clients – Main Loop




// Create a thread for each client
while (1) {
    // Receive message from client
    bytesRead = recvfrom(serverSocket, buffer, sizeof(buffer), 0, (struct sockaddr*)&clients[clientCount].address, &clientLen);
    if (bytesRead == -1) {
        perror("Receive failed");
        exit(EXIT_FAILURE);
    }
 
    buffer[bytesRead] = '\0';
    printf("Client %d connected: %s", clientCount + 1, buffer);
 
    // Add client to the list
    clients[clientCount].socket = serverSocket;
    clients[clientCount].id = clientCount;
    pthread_create(&threads[clientCount], NULL, handleClient, (void*)&clients[clientCount].id);
    clientCount++;
 
    // Send a welcome message to the client (for demonstration purposes)
    sprintf(buffer, "Server: Welcome, you are Client %d\n", clientCount);
    sendto(clients[clientCount - 1].socket, buffer, strlen(buffer), 0, (struct sockaddr*)&clients[clientCount - 1].address, sizeof(clients[clientCount - 1].address));
}

Step 4: Handling Individual Clients in Threads




void *handleClient(void *arg) {
    int id = *((int *)arg);
    char buffer[MAX_MESSAGE_SIZE];
    ssize_t bytesRead;
 
    while (1) {
        // Receive message from client
        bytesRead = recvfrom(clients[id].socket, buffer, sizeof(buffer), 0, NULL, NULL);
        if (bytesRead == -1) {
            perror("Receive failed");
            exit(EXIT_FAILURE);
        }
 
        buffer[bytesRead] = '\0';
        printf("Client %d: %s", id + 1, buffer);
 
        // Broadcast the received message to all clients
        pthread_mutex_lock(&mutex);
        for (int i = 0; i < clientCount; ++i) {
            if (i != id) {
                sendto(clients[i].socket, buffer, bytesRead, 0, (struct sockaddr*)&clients[i].address, sizeof(clients[i].address));
            }
        }
        pthread_mutex_unlock(&mutex);
 
        // Send a message to the client (for demonstration purposes)
        sprintf(buffer, "Server: Message from server to Client %d\n", id + 1);
        sendto(clients[id].socket, buffer, strlen(buffer), 0, (struct sockaddr*)&clients[id].address, sizeof(clients[id].address));
    }
}

Step 5: Cleanup and Program Termination




close(serverSocket);
pthread_mutex_destroy(&mutex);
 
return 0;

Step 6: Write the Complete Code

Now, write the complete code on the server.c file.






#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#define PORT 8888
#define MAX_MESSAGE_SIZE 1024
#define MAX_CLIENTS 10
 
typedef struct {
    struct sockaddr_in address;
    int socket;
    int id;
} Client;
 
Client clients[MAX_CLIENTS];
pthread_t threads[MAX_CLIENTS];
int clientCount = 0;
pthread_mutex_t mutex;
 
void *handleClient(void *arg) {
    int id = *((int *)arg);
    char buffer[MAX_MESSAGE_SIZE];
    ssize_t bytesRead;
 
    while (1) {
        // Receive message from client
        bytesRead = recvfrom(clients[id].socket, buffer, sizeof(buffer), 0, NULL, NULL);
        if (bytesRead == -1) {
            perror("Receive failed");
            exit(EXIT_FAILURE);
        }
 
        buffer[bytesRead] = '\0';
        printf("Client %d: %s", id + 1, buffer);
 
        // Broadcast the received message to all clients
        pthread_mutex_lock(&mutex);
        for (int i = 0; i < clientCount; ++i) {
            if (i != id) {
                sendto(clients[i].socket, buffer, bytesRead, 0, (struct sockaddr*)&clients[i].address, sizeof(clients[i].address));
            }
        }
        pthread_mutex_unlock(&mutex);
 
        // Send a message to the client (for demonstration purposes)
        sprintf(buffer, "Server: Message from server to Client %d\n", id + 1);
        sendto(clients[id].socket, buffer, strlen(buffer), 0, (struct sockaddr*)&clients[id].address, sizeof(clients[id].address));
    }
}
 
int main() {
    int serverSocket;
    struct sockaddr_in serverAddr;
    char buffer[MAX_MESSAGE_SIZE];
    socklen_t clientLen = sizeof(struct sockaddr_in);
    ssize_t bytesRead;
 
    // Create socket
    if ((serverSocket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }
 
    // Configure server address
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(PORT);
 
    // Bind socket
    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }
 
    // Initialize mutex
    pthread_mutex_init(&mutex, NULL);
 
    printf("Server listening on port %d...\n", PORT);
 
    // Create a thread for each client
    while (1) {
        // Receive message from client
        bytesRead = recvfrom(serverSocket, buffer, sizeof(buffer), 0, (struct sockaddr*)&clients[clientCount].address, &clientLen);
        if (bytesRead == -1) {
            perror("Receive failed");
            exit(EXIT_FAILURE);
        }
 
        buffer[bytesRead] = '\0';
        printf("Client %d connected: %s", clientCount + 1, buffer);
 
        // Add client to the list
        clients[clientCount].socket = serverSocket;
        clients[clientCount].id = clientCount;
        pthread_create(&threads[clientCount], NULL, handleClient, (void*)&clients[clientCount].id);
        clientCount++;
 
        // Send a welcome message to the client (for demonstration purposes)
        sprintf(buffer, "Server: Welcome, you are Client %d\n", clientCount);
        sendto(clients[clientCount - 1].socket, buffer, strlen(buffer), 0, (struct sockaddr*)&clients[clientCount - 1].address, sizeof(clients[clientCount - 1].address));
    }
 
    close(serverSocket);
    pthread_mutex_destroy(&mutex);
 
    return 0;
}

Server Script Code

Creating Client Script

In this section, we will create the Client script through which multiple clients can connect to the server for data transfer and message transfer.

Step 1: Create a client.c File

Firstly, create the client.c file in the nano editor by using the command in the terminal.

nano client.c

Creating client.c file

Step 2: Client Initialization and Receive Thread




typedef struct {
    struct sockaddr_in serverAddr;
    int socket;
} Client;
 
Client client;
 
void *receiveMessages(void *arg) {
    char buffer[MAX_MESSAGE_SIZE];
    ssize_t bytesRead;
 
    while (1) {
        bytesRead = recvfrom(client.socket, buffer, sizeof(buffer), 0, NULL, NULL);
        if (bytesRead == -1) {
            perror("Receive failed");
            exit(EXIT_FAILURE);
        }
 
        buffer[bytesRead] = '\0';
        printf("Server: %s", buffer);
    }
}

Step 3: Socket Creation and Message Sending




int main() {
    pthread_t receiveThread;
    char message[MAX_MESSAGE_SIZE];
    ssize_t bytesRead;
 
    // Create socket
    if ((client.socket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }
 
    // Configure server address
    memset(&client.serverAddr, 0, sizeof(client.serverAddr));
    client.serverAddr.sin_family = AF_INET;
    client.serverAddr.sin_port = htons(PORT);
    if (inet_aton("127.0.0.1", &client.serverAddr.sin_addr) == 0) {
        perror("Invalid server address");
        exit(EXIT_FAILURE);
    }
 
    // Create a thread to receive messages
    pthread_create(&receiveThread, NULL, receiveMessages, NULL);
 
    // Send and receive messages
    while (1) {
        // Get user input
        printf("Enter message: ");
        fgets(message, sizeof(message), stdin);
 
        // Send message to server
        sendto(client.socket, message, strlen(message), 0, (struct sockaddr*)&client.serverAddr, sizeof(client.serverAddr));
    }
 
    close(client.socket);
 
    return 0;
}

Step 4: Receive Thread Function




void *receiveMessages(void *arg) {
    char buffer[MAX_MESSAGE_SIZE];
    ssize_t bytesRead;
 
    while (1) {
        bytesRead = recvfrom(client.socket, buffer, sizeof(buffer), 0, NULL, NULL);
        if (bytesRead == -1) {
            perror("Receive failed");
            exit(EXIT_FAILURE);
        }
 
        buffer[bytesRead] = '\0';
        printf("Server: %s", buffer);
    }
}

Step 5: Write the Entire Code

Now, write the entire code in the client.c which is stated below in the code block.




#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PORT 8888
#define MAX_MESSAGE_SIZE 1024
 
typedef struct {
    struct sockaddr_in serverAddr;
    int socket;
} Client;
 
Client client;
 
void* receiveMessages(void* arg)
{
    char buffer[MAX_MESSAGE_SIZE];
    ssize_t bytesRead;
 
    while (1) {
        bytesRead = recvfrom(client.socket, buffer,
                             sizeof(buffer), 0, NULL, NULL);
        if (bytesRead == -1) {
            perror("Receive failed");
            exit(EXIT_FAILURE);
        }
 
        buffer[bytesRead] = '\0';
        printf("Server: %s", buffer);
    }
}
 
int main()
{
    pthread_t receiveThread;
    char message[MAX_MESSAGE_SIZE];
    ssize_t bytesRead;
 
    // Create socket
    if ((client.socket = socket(AF_INET, SOCK_DGRAM, 0))
        == -1) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }
 
    // Configure server address
    memset(&client.serverAddr, 0,
           sizeof(client.serverAddr));
    client.serverAddr.sin_family = AF_INET;
    client.serverAddr.sin_port = htons(PORT);
    if (inet_aton("127.0.0.1", &client.serverAddr.sin_addr)
        == 0) {
        perror("Invalid server address");
        exit(EXIT_FAILURE);
    }
 
    // Create a thread to receive messages
    pthread_create(&receiveThread, NULL, receiveMessages,
                   NULL);
 
    // Send and receive messages
    while (1) {
        // Get user input
        printf("Enter message: ");
        fgets(message, sizeof(message), stdin);
 
        // Send message to server
        sendto(client.socket, message, strlen(message), 0,
               (struct sockaddr*)&client.serverAddr,
               sizeof(client.serverAddr));
    }
 
    close(client.socket);
 
    return 0;
}

Client Script Code

Steps to Execute Scripts

Step 1: Compile Server and Client Scripts

Once we have created the scripts, we need to compile them with the gcc compiler. So execute the below commands in the terminal to compile it successfully:

gcc server.c -o server -lpthread
gcc client.c -o client -lpthread

Compiling Scripts

Step 2: Data Exchange between Server-Client

In the below video output, we can see that we have executed server and multiple client scripts.

Output

Conclusion

In conclusion, the provided C code establishes a simple UDP chat client capable of sending and receiving messages to and from a server. The program utilizes multithreading, with a dedicated thread for continuously receiving messages from the server while the main thread handles user input and message transmission. The client initiates a UDP socket, configures the server address, and creates a separate thread for message reception.


Article Tags :