C++

Socket Programming in C++

Socket programming has become an important subject in the field of computer networking. It involves establishing a connection between two nodes, server, and client to communicate with each other without any interruption. The server acts as a listener in the communication channel and listens to the client on a specific port at an IP address. On the other hand, the client acts as a communicator in the communication channel. The client contacts the server in order to create a connection and make a contact with the server. This article aims to provide a comprehensive and detailed guide to socket programming in C++, covering the basics, presenting practical examples, and providing a detailed explanation of the code.

Establishing the Client-Server Model

Socket programming is the process that builds a communication channel between the server and the client using sockets. In the following example code, the client starts a contact with the server, and the server is set up to accept the client connections. Let us understand the server and client code segments by demonstrating their core working within the network communication. The following is the server-side code. Let us see the code first and then explain the code in detail, point by point.

1. Server Side

The code for the server side of the model is given in the following. Let us see what is happening in the code:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>

using namespace std;

#define PORT 8080
#define MAX_BUF_SIZE 1024

int main() {
    int ser_socket, cli_socket;
    struct sockaddr_in ser_address, cli_address;
    char buf[MAX_BUF_SIZE] = {0};

    if ((ser_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("Error in the Socket creation");
        exit(EXIT_FAILURE);
    }

    ser_address.sin_family = AF_INET;
    ser_address.sin_addr.s_addr = INADDR_ANY;
    ser_address.sin_port = htons(PORT);

    if (bind(ser_socket, (struct sockaddr*)&ser_address, sizeof(ser_address)) == -1) {
        perror("Failure in bind");
        exit(EXIT_FAILURE);
    }

    if (listen(ser_socket, 3) == -1) {
        perror("Failed to Listen");
        exit(EXIT_FAILURE);
    }

    cout << "Server listening on port " << PORT << "...\n";

    socklen_t cli_address_len = sizeof(cli_address);
    if ((cli_socket = accept(ser_socket, (struct sockaddr*)&cli_address, &cli_address_len)) == -1) {
        perror("Failed to accept");
        exit(EXIT_FAILURE);
    }

    read(cli_socket, buf, MAX_BUF_SIZE);
    cout << "Client's message is: " << buf <<endl;

    send(cli_socket, "Server's message", strlen("Server's message"), 0);

    close(cli_socket);
    close(ser_socket);

    return 0;
}

The given example is the server-side code of the C++ program. This code works for a simple TCP server to listen for connections on a single specific port. When a connection is successfully created, the server will receive a message that is sent from the client. After that, it prints it on the console and sends a response message to the client. Let us understand each line of code.

The program starts with including the libraries: “iostream” for standard input/output definitions, “cstring” for string handling functions, “unistd.h” to provide access to the POSIX operating system API, and “arpa/inet.h” to perform the internet operations. The “#define PORT 8080” statement means it defines the port number 8080 on which the server will listen. The “#define MAX_BUF_SIZE 1024” means the maximum buffer size for the incoming data which is 1024.

In the main function, two variables are initialized, “ser_socket” and “cli_socket”, to represent both the server and client, respectively. The other three variables which are “sockaddr_in”, “ser_address”, and “cli_address” of type “struct” are initialized as address structures for the server and client. After that, a buffer named “buf” is initialized which stores the data that comes from the client.

The socket() function in the “if” condition creates a new TCP socket. AF_INET denotes IPv4, SOCK_STREAM represents the connection-oriented and reliable TCP socket, the last argument which is 0 is given to select the default TCP protocol, INADDR_ANY accepts the connections on any IP address, and htons (PORT) converts the port number from the host byte order to the network byte order.

Since everything is defined properly, the next step is to set up the server as a lister on the given port and accept the connections on any network interface. The socket is given with the information in “ser_address” by the bind() method. We print an error and end the process if the binding fails. The accept() function opens a new socket for the connection with the client, whereas the listen() function instructs the server to wait for incoming connections. If the accept() function fails, the error message is printed and the function will exit.

Next, the server reads the client message with the read() function into the “buf” buffer and then print it to the console. The send() function is used by the server to send a message in response to the client. Lastly, using close(), the server closes the client's socket, terminating the program so that all connections are closed properly and there is no probability of data breach.

2. Client Side

Now, let’s see what happens in the client model:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define SERVER_IP "127.0.0.1"

int main() {
    int cli_socket;
    struct sockaddr_in ser_address;
    const char* mesg = "Client is sending greetings!";

    if ((cli_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("Error in socket creation");
        exit(EXIT_FAILURE);
    }

    ser_address.sin_family = AF_INET;
    ser_address.sin_port = htons(PORT);

    if (inet_pton(AF_INET, SERVER_IP, &ser_address.sin_addr) <= 0) {
        perror("Wrong address");
        exit(EXIT_FAILURE);
    }

    if (connect(cli_socket, (struct sockaddr*)&ser_address, sizeof(ser_address)) == -1) {
        perror("Connection failure");
        exit(EXIT_FAILURE);
    }
    send(cli_socket, mesg, strlen(mesg), 0);

    char buf[1024] = {0};
    read(cli_socket, buf, sizeof(buf));
    std::cout << "Server response: " << buf << std::endl;

    close(cli_socket);
    return 0;
}

Let us see each line of code to understand how the program works.

The same four libraries – iostream, cstring, unistd.h, and arpa/inet.h – are also included on the client side. A port number is also defined along with the IP address of the local host 127.0.0.1. The message that has to be delivered to the server is given. The client and server need to establish a connection as the following step:

The “if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1);” creates a socket for IPv4 with a stream type and the default protocol TCP. The perror() prints the error details if the socket() function fails to establish a connection and exits the program.

The “server_address.sin_port = htons(PORT);” sets the port number after converting to the network byte order. Afterward, another failure message which is the “Wrong address” is given here which is printed if there is something wrong with the address. By locating the address in the “ser_address”, the client will connect to the server. If the connection fails, the error details are printed. The send() function will transfer the message to the server, ensuring that it does not contain any flag.

To receive and store a response from the server, a buffer named “buf” of type “char” is initialized. The read() function reads the server's response into the buffer. Finally, the server's response is printed to the console. Finally, the connection is closed using the close() statement to terminate the socket. The following is the output of the program:

Conclusion

Socket programming is an important part of network communication in computer science. It enables the development of applications that can communicate over the network, enabling a wide range of possibilities from simple client-server architectures to structured distributed systems. When a socket is created in a programming context, the program must configure its endpoint characteristics such as the protocols, TCP or UDP, and the network address like the IP address and port number. These sockets let the servers send and receive the data. This article demonstrates a practical example on how the client-server model in socket programming works.

About the author

Kalsoom Bibi

Hello, I am a freelance writer and usually write for Linux and other technology related content