Open In App

How to create a multi-chat server using UDP?

Last Updated : 26 Dec, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

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

Creating server.c file

Step 2: Server Initialization and Socket Binding

C




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);


  • Socket Creation: The server uses the socket system call to create a UDP socket.
  • Server Address Configuration: The server’s address structure (serverAddr) is configured with the server’s IP, port, and protocol details.
  • Binding: The socket is bound to the server’s address using the bind system call.
  • Mutex Initialization: A mutex (pthread_mutex_t mutex) is initialized for thread synchronization.
  • Print Server Listening: A message is printed indicating that the server is listening on a specific port.

Step 3: Handling Clients – Main Loop

C




// 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));
}


  • Client Connection Handling: Inside an infinite loop, the server continuously receives messages from clients using recvfrom.
  • Client Information Printing: Information about the connected client is printed, and the client’s message is stored in the buffer.
  • Client Registration: The client’s socket, id, and address are stored in the client’s array.
  • Thread Creation: A new thread is created for each connected client using pthread_create, and the handleClient function is called.
  • Welcome Message: A welcome message is sent to the client for demonstration purposes.

Step 4: Handling Individual Clients in Threads

C




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));
    }
}


  • Thread Function Definition: The handleClient function is defined to handle communication for each connected client in a separate thread.
  • Message Reception: The thread continuously receives messages from its corresponding client using recvfrom.
  • Message Broadcast: The received message is broadcasted to all clients except the sender using a mutex for thread safety.
  • Server Message Sending: A message from the server to the client is sent for demonstration purposes.

Step 5: Cleanup and Program Termination

C




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


  • Socket Closure: The server socket is closed using close.
  • Mutex Destruction: The mutex is destroyed using pthread_mutex_destroy.
  • Program Termination: The program returns 0 to indicate successful termination.

Step 6: Write the Complete Code

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

C




#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

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

Creating client.c file

Step 2: Client Initialization and Receive Thread

C




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);
    }
}


  • Client Structure Definition: A structure (Client) is defined to hold information about the server address and the client socket.
  • Client Initialization: An instance of the Client structure (client) is created to store client-related information.
  • Receive Thread Function: The receiveMessages function is defined to handle continuous message reception from the server in a separate thread.

Step 3: Socket Creation and Message Sending

C




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;
}


  • Socket Creation: A UDP socket is created using the socket system call.
  • Server Address Configuration: The server address is configured with the server’s IP, port, and protocol details.
  • Thread Creation: A thread (receiveThread) is created to run the receiveMessages function concurrently.
  • Message Sending Loop: Inside an infinite loop, the client continuously prompts the user for input, sends the message to the server using sendto, and then repeats the process.
  • Socket Closure: When the program terminates, the client socket is closed using close.

Step 4: Receive Thread Function

C




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);
    }
}


  • Thread Function Definition: The receiveMessages function continuously receives messages from the server using recvfrom.
  • Buffer Initialization: A buffer (buffer) is used to store the received message.
  • Error Handling: If the reception fails, an error message is printed, and the program exits with an error status.
  • Message Printing: The received message is printed to the console with a prefix indicating it’s from the server.

Step 5: Write the Entire Code

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

C




#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

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

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

Output

  • The message sent from each client is been managed by the server giving it responsibility.
  • The server handles multiple clients in thread form.
  • Here, UDP broadcasting broadcasts messages in a multi-client environment, which ensures a proper communication process.
  • Due to UDP broadcast, the messages are broadcasted among all the machines including the clients, the message sent by the client is also broadcasted to other clients including the server, and vice versa.

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.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads