Introduction

In Linux, “socket” is a type of Inter-Process Communication (IPC) mechanism that allows processes to communicate with each other over a network or within a single system. Sockets provide a common interface for sending and receiving data, regardless of the underlying communication protocol.

Types of Sockets

There are two types of sockets in Linux:

  1. Local sockets (AF_UNIX or AF_LOCAL): These sockets allow communication between processes running on the same system. They use the file system as the communication medium, and the data is sent and received using file descriptor. Local sockets are also called UNIX domain sockets.
  2. Network sockets (AF_INET or AF_INET6): These sockets allow communication between processes running on different systems over a network. They use the network protocol stack as the communication medium, and the data is sent and received using IP addresses and ports. Network sockets are also called Internet domain sockets.

Both types of sockets can be used in a variety of ways, such as:

  • Stream sockets: These sockets provide a reliable, two-way, connection-oriented communication channel. They are based on the Transmission Control Protocol (TCP) and are often used for applications such as file transfer, email, and web browsing.
  • Datagram sockets: These sockets provide a connectionless, unreliable communication channel. They are based on the User Datagram Protocol (UDP) and are often used for applications such as video streaming, online gaming, and telephony.
  • Raw sockets: These sockets allow direct access to the underlying communication protocols, and can be used for creating custom protocols or for low-level network troubleshooting.
Socket: an IPC communication method
Socket communication

In Linux, sockets are implemented as a file descriptor. A socket descriptor is created using the socket() system call, and the communication is done using read() and write() system calls. The socket descriptor can also be used with the select() or poll() system calls for multiplexing multiple communication channels.

Now, let us discuss the steps done at server and client side for the socket communication.

Server Socket Creation

n socket communication, the server side needs to perform the following steps to establish a connection with a client:

  1. Create a socket descriptor: The server creates a socket descriptor using the socket() system call. This descriptor is used to identify the socket and is used for all further communication.
  2. Bind the socket to an IP address and port: The server uses the bind() system call to associate the socket with a specific IP address and port. This IP address and port are used by the client to connect to the server.
  3. Listen for incoming connections: The server uses the listen() system call to specify the maximum number of clients that can be connected to the server simultaneously. The server is now ready to accept incoming connections from clients.
  4. Accept incoming connections: The server uses the accept() system call to accept incoming connections from clients. When a client connects, the accept() call returns a new socket descriptor that is used for communication with that specific client.
  5. Receive and send data: Once the connection is established, the server can use the read() and write() system calls or the recv() and send() system calls to receive and send data to the client.
  6. Close the connection: After the communication is finished, the server uses the close() system call to close the connection with the client. The socket descriptor is also closed using the close() system call.

Lets us try it with C code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT_NO 1234    // Port number
#define MAX_CLIENTS 5   // Maximum number of clients

int main(int argc, char *argv[])
{
    int sockfd, newsockfd, portno;
    socklen_t client ;
    char buffer[256];
    struct sockaddr_in serv_addr, cli_addr;
    int n;

    // Create a socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("ERROR opening socket");
        exit(1);
    }
    
    // Initialize socket structure
    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = PORT_NO;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    // Bind the host address
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        perror("ERROR on binding");
        exit(1);
    }

    // Listen for incoming connections
    listen(sockfd, MAX_CLIENTS);
    client = sizeof(cli_addr);

    // Accept incoming connections
    while (1) {
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &client);
        if (newsockfd < 0) {
            perror("ERROR on accept");
            exit(1);
        }

        // Read and write data to the client
        bzero(buffer, 256);
        n = read(newsockfd, buffer, 255);
        if (n < 0) {
            perror("ERROR reading from socket");
            exit(1);
        }

        printf("Received message: %s\n", buffer);
        n = write(newsockfd, buffer, strlen(buffer));
        if (n < 0) {
            perror("ERROR writing to socket");
            exit(1);
        }

        // Close the connection
        close(newsockfd);
    }

    close(sockfd);
    return 0;
}

This example creates a socket, binds it to the IP address INADDR_ANY and port PORT_NO, and listens for incoming connections with a maximum of MAX_CLIENTS. It then enters an infinite loop to accept incoming connections and echoes back any data received from the client. Once the communication is finished, the connection is closed using the close() system call.

It’s worth noting that before binding the socket to an IP and port, it’s necessary to set the socket options and configurations like protocol, address family, type of socket and so on. Also, once the socket is created, the server should be ready to handle multiple clients, to do that it’s common to use a loop and a multiplexing mechanism like select() or poll() to handle multiple clients at the same time.

Additionally, it is also important to handle errors and exceptions in the server-side code, as well as to properly handle unexpected termination of the client’s connection.

Client Socket Creation

At the client side, the following steps are typically performed to establish a socket connection with a server:

  1. Create a socket descriptor: The client creates a socket descriptor using the socket() system call. This descriptor is used to identify the socket and is used for all further communication.
  2. Connect to the server: The client uses the connect() system call to establish a connection with the server. The connect() call takes the socket descriptor and the server’s IP address and port number as arguments.
  3. Send and receive data: Once the connection is established, the client can use the write() and read() system calls or the send() and recv() system calls to send and receive data to/from the server.
  4. Close the connection: After the communication is finished, the client uses the close() system call to close the connection with the server. The socket descriptor is also closed using the close() system call.

It’s worth noting that before connecting to the server, it’s necessary to set the socket options and configurations like protocol, address family, type of socket and so on. Also, it’s important to handle errors and exceptions in the client-side code, as well as to properly handle unexpected termination of the server’s connection.

Here is an example of a simple socket client in C language that connects to a server and sends a message:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 

int main(int argc, char *argv[])
{
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;

    char buffer[256];
    if (argc < 3) {
       fprintf(stderr,"usage %s hostname port\n", argv[0]);
       exit(0);
    }
    portno = atoi(argv[2]);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
        error("ERROR opening socket");
    
    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
    serv_addr.sin_port = htons(portno);

    if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
        error("ERROR connecting");

    printf("Please enter the message: ");
    bzero(buffer,256);
    fgets(buffer,255,stdin);
    
    n = write(sockfd,buffer,strlen(buffer));
    if (n < 0)
        error("ERROR writing to socket");

    bzero(buffer,256);
    n = read(sockfd,buffer,255);
    if (n < 0)
        error("ERROR reading from socket");

    printf("%s\n",buffer);

    close(sockfd);
    return 0;
}

In this code, the client takes the server’s hostname and port number as command-line arguments, creates a socket, and uses the connect() system call to establish a connection with the server. It then prompts the user for a message, sends the message to the server using the write() system call, and receives the echo message from the server using the read() system call. Finally, it closes the connection with the server using the close() system call.

Conclusion

Sockets are a powerful and flexible IPC mechanism and are widely used in Linux for network communication and inter-process communication. They are often used in conjunction with other IPC mechanisms such as pipes, message queues, and shared memory to create more complex communication systems.

Reference

One thought on “Socket: an IPC mechanism”

Comments are closed.