×

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

How to Declare Unordered Sets in C++

The implementation of an unordered set using a hash table ensures that the insertion is always randomised by hashing the keys into hash table indices. When we define keys of...

4 minutes read.

Leap Year Program in C++

What is a Leap Year? A solar year is the length of time that it takes for Earth to orbit the Sun - approximately 365.25 days. In a calendar year, we...

4 minutes read.

Stringstream in C++ and its applications

In this tutorial, we will explore what the stringstream in C++ is. We will also learn its application. What is stringstream? With the aid of a stringstream, user can read from a...

2 minutes read.

C++ Prime number program

In this lesson, you'll learn how to verify whether a given number is a prime number or not in C++, and you'll obtain code to do it. What is the definition...

3 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.

strcat() vs strncat() in C++

In this tutorial, we will explore about strcat() and strncat() in the most usable language C++. We will also look at the difference between them. strcat() C++ is a computer language with...

4 minutes read.

Iostream in C++

Using Iostream in C++, we can perform input and output operation capabilities. This represents input and output, and the stream is used to carry out this capability. A stream is...

4 minutes read.

C++ Enumeration

C++ Enumeration In C++, Enum is a special data type that contains some fixed sets of components that have various applications in the programming. Enum works fine with fixed constant sets...

3 minutes read.

C++ String Class and its Applications

The String class is available in C++. The character array is represented by the C string. The string class in C++ has a few different attributes. It contains several functions...

4 minutes read.

C++ Iterators

What are iterators ? Iterators are among the four foundations of the C++ Standard Template Library, also known as the STL. The memory address of the STL container classes is pointed...

15 minutes read.

Learn C++ Tutorial

C++ Introduction C++ is an object-oriented programming language. It was developed by Bjarne Stroustrup at AT&T Bell Laboratories. It is superset (extension) of C programming language. Depending upon features supported by programming...

10 minutes read.

Swap numbers in C++

Swap numbers Swapping refers to interchanging values between two variables. Swapping is important and easy to understand programming logic in the world of coding. Though it is used in the programming...

4 minutes read.

Upcasting and Downcasting in C++

With the help of various examples in the C++ programming language, this section will cover Upcasting and Downcasting. Upcasting and downcasting, on the other hand, are two forms of object typecasting. Consider...

3 minutes read.

For Loop Examples in C++

For Loop A for loop is a repetitive control structure that allows you to create a loop to execute a specific number of times efficiently. The syntax of for loop In C++, a...

6 minutes read.

How to initialize a dynamic array in C++

Regular arrays or static arrays have a predetermined size or fixed size. Change in the size of regular arrays is not possible. The memory size for static arrays determines at compile...

4 minutes read.

Print Table Using 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.

C++ File Handling

File handling is a mechanism that manipulates the data stored in files. File handling store output data from the program to external file and read file data to the program. There...

3 minutes read.

C++ Maximum Index Problem

Given an array A[] of positive integers. We will find the maximum of (j-i) such that i and j are the indexes of A[] and A[i] <= A[j], i<=j For...

5 minutes read.

C++ Program to Print Fibonacci Triangle

Fibonacci Triangle Program in CPP Definition: Fibonacci Triangle as the name suggests is the same as the Fibonacci number series where the next element is the sum of the previous two elements....

3 minutes read.

wcscpy(), wcslen(), wcscmp() Functions in C++

There are many built-in functions in C++ programming language which differentiate it from C programming language in most hardware-coded languages. We will now closely look into the applications of three...

4 minutes read.