×

Handling multiple clients on the Server with multithreading using Socket Programming in C or C++

To understand this guide completely, the reader is assumed to be familiar with the foundations of server and client models and socket programming. If one wants to create any scalable server model, it is a significant assumption that the Server can only handle one client at a time in the basic model. The simplest method for managing numerous clients would be to create a new thread for each additional client that joins the service.

Let's start by learning about socket programming.

Socket programming in C/C++

Using socket programming, two nodes on a network can connect and communicate with one another. While the second socket (node) tries to connect, the first socket listens on a specific port at an IP address. The listener socket is formed as the client and server communicate. It contains several server stages.

Stages for the Server

1. socket creation

int sockfd = socket(domain, type, protocol)
  • Sockfd: It is a socket descriptor, an integer(it is like a file handle)
  • Domain: Specifies the communication domain as an integer. We use the AF_ LOCAL capability offered by the POSIX standard to facilitate communication between processes on the same host. We use AF INET and AF I NET 6 for processes connected by IPV4 and IPV6, respectively, to communicate between processes on various hosts.
  • type: the form of communication
    TCP: SOCK STREAM (reliable, connection-oriented)
    UDP SOCK DGRAM (unreliable, connectionless)
  • protocol: Internet Protocol (IP) has a protocol value of 0. The protocol field of an IP packet's header has the same number displayed. For more information, see Man Protocols.

2. Setsockopt

This makes modifying the socket's parameters easier, to which the file descriptor sockfd corresponds. It encourages the reuse of addresses and ports but is entirely optional. Address already in use errors is prevented.

int setsockopt(int level, int optname, int sockfd,  const void *optval, socklen_t optlen);

3. Bind

The bind function links the newly created socket to the address and port number supplied in the addr after socket creation (custom data structure). The Server is bound to localhost; therefore, the IP address is specified using INADDR_ANY.

4. Listen

When this happens, the server socket enters a passive mode and waits for a client to connect to it. The backlog establishes the maximum size the sockfd queue of open connections may extend. A client may get an error with the code ECONNREFUSED if a connection request comes in when the queue is full.

5. Accept

The initial connection request from the list of pending connections is used to construct a new connected socket for the listening socket, sockfd, which then returns a fresh file descriptor for that socket. Having established a connection, the client and server can now start exchanging data.

Stages for client's

  1. socket connection: identical to the process for creating a server-side socket.
  2. Connect: The socket to which the file descriptor sockfd refers and the address given by addr are connected by the system function connect().Addr contains the Server's address and port information.
    int connect(int sockfd, const struct sockaddr *addr, socklen_t address);
  3. Implementation: In this case, we are sending a single hello message between the Server and client to demonstrate the client/server notion.

Server. c

Lets see server-side C/C++ program to demonstrate socket programming

#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080
int main(int argc, char const* argv[])
{
    int server_fd, new_socket, valread;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = { 0 };
    char* hey = "Hey there from server";
  
    // the creation of socket file descriptors
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0))
        == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
  
    // requiring the connection of a socket to port 8080
    if (setsockopt(server_fd, SOL_SOCKET,
                   SO_REUSEADDR | SO_REUSEPORT, &opt,
                   sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
  
    // requiring the connection of a socket to port 8080
    if (bind(server_fd, (struct sockaddr*)&address,
             sizeof(address))
        < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    if ((new_socket
         = accept(server_fd, (struct sockaddr*)&address,
                  (socklen_t*)&addrlen))
        < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    valread = read(new_socket, buffer, 1024);
    printf("%s\n", buffer);
    send(new_socket, hey, strlen(hey), 0);
    printf("Hey there\n");
    
  // the connecting socket is closed
    close(new_socket);
  // the connecting socket is closed
    shutdown(server_fd, SHUT_RDWR);
    return 0;
   }

Client.c

Lets see server-side C/C++ program to demonstrate socket

#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080
  
int main(int argc, char const* argv[])
{
    int sock = 0, valread, client_fd;
    struct sockaddr_in serv_addr;
    char* hello = "Hey there from client";
    char buffer[1024] = { 0 };
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }
  
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
  
    // IPv4 and IPv6 text addresses can be converted to binary.
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)
        <= 0) {
        printf(
            "\nInvalid address/ Address not supported \n");
        return -1;
    }
  
    if ((client_fd
         = connect(sock, (struct sockaddr*)&serv_addr,
                   sizeof(serv_addr)))
        < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }
    send(sock, hey, strlen(hey), 0);
    printf("Hey there message sent\n");
    valread = read(sock, buffer, 1024);
    printf("%s\n", buffer);
  
    // the connecting socket is closed
    close(client_fd);
    return 0;
}

Output:

Handling multiple clients on the Server with multithreading using Socket Programming in C/C++

Server and client models

A distributed application structure known as the client-server paradigm divides tasks or workloads between providers, servers, and clients, who serve those who are asking for the resource or service. A client-server architecture is what this is. A server receives a request for data sent over the internet by a client computer, processes the request, and then transmits the requested data packets back to the client. There is no client-to-client resource sharing. Several client-server architecture examples include email and the World Wide Web.

How are servers and browsers connected?

Interacting with the servers as a client only requires a few simple actions.

  • The user types the website's or file's URL (Uniform Resource Locator).
  • A request is made by the browser to the DNS(DOMAIN NAME SYSTEM) Server.
  • For the WEB Server's address, use a DNS server lookup.
  • The web server's IP address is returned by the DNS server in response.
  • Using the web server's IP address, a browser sends an HTTP/HTTPS request (provided by the DNS server).
  • The Server transmits the website's required files.
  • The website is subsequently displayed once the browser renders the files.
    The DOM (Document Object Model) interpreter, CSS interpreter, and JS Engine—collectively known as JIT or (Just in Time) Compilers—are used to render this content.

Client-server model benefits

  • A Centralised database containing all information in one location.
  • Data recovery is achievable, and costs are kept to a minimum.
  • The client and the Server can be altered and can alter the Client and Server capacities separately.

Client-Server model drawbacks

  • If viruses, Trojan horses, and worms are uploaded to or present on the Server, clients are vulnerable to them.
  • Denial of Service (DOS) assaults can be very damaging to servers.
  • During transmission, data packets can be tampered with or altered.

Semaphores: Simple non-negative variable shared by several threads, semaphore. This variable is used in the multiprocessing environment to achieve process synchronisation and resolve the critical section problem.

sem_post: The semaphore that sem points to is increased (unlocked) by sem post(). A process or thread stopped in a sem wait(3) call will be awakened and proceed to lock the semaphore if the semaphore's value subsequently increases to greater than zero.

#include <semaphore.h>
int sem_post(sem_t *sem);

sem_wait: The semaphore pointed to by sem is decremented (locked) by sem wait(). If the semaphore's value is higher than zero, the decrement will start, and the function will immediately return. If the semaphore's value is zero, the call will block until either the decrement can be performed (i.e., the semaphore value increases) or a signal handler stops the call.

Implementation: create two distinct threads for the server side: a reader thread and a writer thread. Create a serverSocket, an integer variable that will hold the return from the socket function, first.ce.

  • serverSocket: Integer socket descriptor (like a file handle).
  • Domain: The communication domain is an integer, such as AF INET (IPv4 protocol) or AF INET6 (IPv6 protocol).
  • Type: a form of communication.
  • SOCK_STREAM: TCP(connection-oriented, reliable).
  • SOCK_DGRAM: UDP(connectionless, unreliable).
  • Protocol: Internet Protocol (IP) has a protocol value of 0. (See man protocols for additional information.). The protocol field in a packet's IP header contains the same number as this one.

Bind the socket when all relevant variables have been initialised.

Bind: The bind function ties the newly created socket to the address and port number supplied in the addr after it has been created (custom data structure). The Server is bound to the local host in the example code; hence INADDR_ ANY is used to indicate the IP address.

int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);

Approach

  • Receive an integer from the client specifying the option for reading or writing after accepting the connection to the appropriate port. Choice 2 denotes a writer, whereas Choice 1 denotes a reader.
  • Once data has been successfully received, use pthread_create to create reader and writer threads.
  • After making successful connections to the Server, the client-server prompts the user for feedback on the chosen variable.
  • The client then creates a client thread for the request and, after receiving the user's choice, sends it to the Server to call the reader or writer thread.

Related Topics

C++ STL Components

C++ STL Components In today’s article, we are going to learn about all the points things that is related to STL in C++ so stay connected because you are going to...

6 minutes read.

OOPs Concepts in C++

C++ Object-Oriented Programming Concepts C++ uses the concept of object-oriented programming. Object Oriented Programming has some prominent features: Object Class Data abstraction Encapsulation Polymorphism Inheritance Message passing Object An object is the basic unit...

2 minutes read.

What are local class and global class in C++

In C++, a class is a fundamental block that achieves object-oriented programming. The class holds its data members and member functions. Objects help to access the data elements and functions....

4 minutes read.

C++ Socket Programming

In this world, computer networking has become very important for sharing of data. Every good programmer has some knowledge about computer networking. Socket programming is one of the critical topics...

6 minutes read.

rand() and srand() in C / C++

In this tutorial, we'll explore the syntax, usage, and examples of the C++ STL functions rand() and srand(). What exactly is rand()? The C++ STL's built-in rand() function is defined in the...

3 minutes read.

Processing strings using std string stream in C++

A string class object called std: string stream is used to streamline into various variables, just as files can pour into strings. This class's things make use of a string...

3 minutes read.

How to run program in turbo c++

What is Turbo C++? Turbo c++ is an integrated development environment (IDE) and a compiler to run C++ code. Turbo c++ helps to link the header files with the main code....

2 minutes read.

Precision of floating point numbers Using these functions floor(), ceil(), trunc(), round() and setprecision() in C++

Precision of floating point numbers Using these functions floor(), ceil(), trunc(), round() and setprecision() in C++ 1/2 decimal equal is 0.555555555555555555555 .... An indefinite number of lengths will require the storage...

4 minutes read.

How to Sort an Array in C++

What is Sorting? Sorting is a process of arranging elements in sequential order, either numerically or alphabetically. The sorting of a numerical array can be accomplished using a variety of algorithms,...

4 minutes read.

How is multiset implemented in C++

Similar to sets, multisets are an associative container type where several items may share the same values. Associative containers implement instantly searchable sorted data structures with O(log n) complexity. In a multiset,...

5 minutes read.

Const_cast in C++ Type Casting Operators

In this tutorial, we will learn enough about const cast in the most widely used programming language, C++, as well as type casting operations. C++ supports the four types of casting...

4 minutes read.

C++ Call by Reference

Call by Reference is a C++ method for passing arguments to a function that enables us to pass the actual memory address of the parameter rather than a copy of...

4 minutes read.

Top best IDEs for C/C++ Developers in 2024

Nothing in the current digital world is conceivable without programming. Everything needs programming, from the cell phones in our pockets to self-driving cars. Programming is also necessary for the mouse...

9 minutes read.

Scope Resolution Operator vs this Pointer in C++

In this tutorial, we will compare the Scope Resolution operation to this Pointer in C++ language. Scope Resolution Operator The Scope Resolution Operator in C++ programming language is usually denoted by (::)....

3 minutes read.

Print Table Using Do while Loop in C++

Multiplication Table In mathematics, a table is created by multiplying a certain number by all of the counting numbers, i.e., 1, 2, 3, 4, 5, 6, and so on. It is...

4 minutes read.

Virtual Functions and Runtime Polymorphism in C++

In this tutorial, we will explore more on virtual functions and runtime polymorphism in the most useful language C++. A virtual function is a member function with the keyword virtual used...

6 minutes read.

C++ Ternary Operator

In this tutorial, we'll learn about the C++ ternary operator and how to utilise it to manage the program's flow using examples. Ternary Operator: The if-else statement and the conditional operator use...

3 minutes read.

C++ Program to Implement Shell Sort

    C++ Program to Implement Shell Sort shell sort is basically an Insertion Sort variant. In the insertion sort, we only transfer elements ahead of one location. Many movements are involved...

2 minutes read.

Check for Balanced Brackets in an Expression (well-formedness) using Stack

Write a program that check the correctness of the pairs and ordering of the characters “{“, “}”, “(“, “)”, “[“, “]” in the expression string exp. Example: Checking for balanced parenthesis is one of...

2 minutes read.

Approach in C++

Object oriented programming languages like Java or C++ use a bottom-up approach that identifies each object first.  In the bottom-up approach, we create a small problem first, and try to...

6 minutes read.