Sockets

Its time for use to understand the sockets and do some socket programming.

What are Sockets❓

At its core, a socket is an abstraction provided by the operating system that allows applications to send or receive data over a network. Think of it as an endpoint in an communication link.

It acts as a connecting point for sending and receiving messages.

A socket is made up of the following components:

  1. IP Address:
    • Identifies the device on the network. It can be an IPv4 or IPv6 address.
  2. Port Number:
    • Identifies the specific process or service on the device that the communication is intended for. For example, HTTP typically uses port 80, and HTTPS uses port 443.
  3. Protocol:
    • Specifies the type of communication protocol being used. Common protocols are:
      1. TCP (Transmission Control Protocol): For reliable, connection-oriented communication.
      2. UDP (User Datagram Protocol): For faster, connectionless communication.
  4. Transport Layer Socket Descriptor:
    • A unique identifier used by the operating system to manage the socket.

Representation:

In code, a socket is usually represented as a combination of:

<IP Address>:<Port Number>

For example:

192.168.1.1:8080 (IPV4)

[2001:db8::1]:443 (IPv6)

Types of Sockets:

  1. Stream Sockets (TCP):
    • Provides reliable, connection-oriented communication.
    • Ensures data integrity, sequencing, and error recovery.
    • Example: Web browsing, email.
  2. Datagram Sockets (UDP):
    • Provides connectionless, unreliable communication.
    • Faster than TCP but does not guarantee delivery.
    • Example: Video streaming, online gaming.
  3. Raw Sockets:
    • Operate at a lower level, giving direct access to IP packets.
    • Typically used for custom protocols or network diagnostics.
  4. Domain Sockets:
    • Used for inter-process communication (IPC) on the same host.
    • Faster than network sockets as they avoid network stack overhead.

Key Purposes of Sockets

  1. Facilitating Network Communication
    • Sockets provide the mechanism for data transfer between devices connected to a network (e.g., the Internet or a local area network).
    • They support a wide variety of use cases, including web browsing, file sharing, and video streaming.
  2. Endpoint Abstraction
    • A socket acts as an endpoint for sending and receiving data.
    • Each socket is uniquely identified by:
      • IP address
      • Port number
      • Protocol (TCP/UDP)
  3. Enabling Client-Server Architecture
    • Sockets form the backbone of the client-server model:
      • Server: Listens on a specific port and accepts incoming connections.
      • Client: Initiates the connection to the server.
    • Examples:
      • Web browsers connect to web servers (e.g., HTTP over TCP).
      • Messaging apps use sockets for real-time communication.
  4. Cross-platform Communication
    • Sockets allow devices running different operating systems to communicate over standard protocols like TCP/IP and UDP.
  5. Protocol Agnosticism
    • Sockets support various protocols, enabling communication that:
      • Is reliable (e.g., TCP for guaranteed delivery).
      • Is fast but less reliable (e.g., UDP for real-time gaming or streaming).
  6. Secure Communication
    • While raw sockets are not inherently secure, they can be used in conjunction with encryption libraries like OpenSSL to enable secure communication (e.g., HTTPS).

Real Life Analogy

Imagine Two Friends Talkin on Tin Can Telephones

  • A socket is like the end of the tin can that each friends hold and speaks into.
  • For the two friends to talk:
    1. Each friend needs their own tin can (a socket).
    2. They must be connected by a string (like a network).
    3. One friend speaks, and the sound travels through the string (data is sent).
    4. The other friend listens and responds (data is received).

Socket Process

The socket process refers to how a socket facilitates communication between two endpoints (client and server) in a network. Here’s an overview of the socket process:

1️⃣ Socket Creation

This is the first step in both the client and server sides. A socket is created as an endpoint for communication.

System Call:

int socket(int domain, int type, int protocol);
  • Domain: Specifies the communication domain.
    • AF_INET: For IPv4 communication.
    • AF_INET6: For IPv6 communication.
    • AF_UNIX: For local communication (on the same machine).
  • Type: Specifies the type of service.
    • SOCK_STREAM: For TCP (connection-oriented, reliable).
    • SOCK_DGRAM: For UDP (connectionless, fast, unreliable).
  • Protocol: Specifies the transport protocol.
    • Usually set to 0 to use the default protocol for the type (e.g., TCP for SOCK_STREAM).

Example:

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
    perror("Socket creation failed");
    exit(EXIT_FAILURE);
}

2️⃣ Server-Side Steps

[a] Bind

The server assigns its socket to a specific IP address and port number so that clients can locate it.

System Call:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • Parameters:
    • sockfd: The socket file descriptor.
    • addr: A pointer to a struct sockaddr that specifies the address and port.
    • addrlen: The size of the address structure.

Example:

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // Bind to all available interfaces
server_addr.sin_port = htons(8080);      // Convert port to network byte order

if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("Bind failed");
    exit(EXIT_FAILURE);
}

[b] Listen

The server prepares the socket to accept incoming connections.

System Call:

int listen(int sockfd, int backlog);
  • Parameters:
    • sockfd: The socket file descriptor.
    • backlog: The number of pending connections the queue can hold.

Example:

if (listen(sockfd, 5) < 0) {
    perror("Listen failed");
    exit(EXIT_FAILURE);
}

[c] Accept

The server accepts an incoming connection, creating a new socket for communication with the client.

System Call:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • Parameters:
    • sockfd: The socket file descriptor.
    • addr: Pointer to a struct sockaddr for storing the client's address.
    • addrlen: Pointer to the size of the address structure.

Example:

struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);

if (new_sockfd < 0) {
    perror("Accept failed");
    exit(EXIT_FAILURE);
}

3️⃣ Client-Side Step

[a] Connect

The client connects to the server using its IP address and port number.

System Call:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • Parameters:
    • sockfd: The socket file descriptor.
    • addr: Pointer to a struct sockaddr with the server's address and port.
    • addrlen: Size of the address structure.

Example:

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080); // Server port
server_addr.sin_addr.s_addr = inet_addr("192.168.1.10"); // Server IP

if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    perror("Connection failed");
    exit(EXIT_FAILURE);
}

4️⃣ Data Transmission

Both client and server can now exchange data over the socket.

[a] Send Data

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • Parameters:
    • sockfd: The socket file descriptor.
    • buf: Pointer to the data buffer to be sent.
    • len: Length of the data.
    • flags: Transmission flags (usually set to 0).

Example:

char message[] = "Hello, Server!";
send(sockfd, message, strlen(message), 0);

[b] Receive Data

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • Parameters:
    • sockfd: The socket file descriptor.
    • buf: Pointer to the buffer for storing received data.
    • len: Size of the buffer.
    • flags: Transmission flags (usually set to 0).

Example:

char buffer[1024];
recv(new_sockfd, buffer, sizeof(buffer), 0);

5️⃣ Socket Closure

Both the client and server close their sockets when communication ends.

System Call:

int close(int sockfd);

Example:

int close(int sockfd);

Detailed Flow Diagram

Server Side:

  1. Create Socket → 2. Bind → 3. Listen → 4. Accept → 5. Communicate (Send/Receive) → 6. Close Socket.

Client Side:

  1. Create Socket → 2. Connect → 3. Communicate (Send/Receive) → 4. Close Socket.

This process enables reliable communication between a client and a server over a network.

Socket Programming

Socket programming is the foundation for enabling communication between devices in a network. It allows developers to write network applications where data can be exchanged between clients and servers using well-defined protocols such as TCP and UDP.

Steps for Socket Programming

1. Server-Side Steps

  1. Create a socket
    Use the socket() function to create a server socket.
  2. Bind the socket
    Associate the socket with an IP address and port number using the bind() function.
  3. Listen for connections
    Put the socket in a passive mode using the listen() function to accept client requests.
  4. Accept a connection
    Use the accept() function to accept an incoming client connection, creating a new socket for communication.
  5. Communicate
    Exchange data with the client using functions like send() and recv().
  6. Close the socket
    Release resources after communication is complete using the close() function.

Client-Side Steps

  1. Create a socket
    Use the socket() function to create a client socket.
  2. Connect to the server
    Use the connect() function to establish a connection with the server.
  3. Communicate
    Exchange data with the server using send() and recv().
  4. Close the socket
    Close the client socket using the close() function.

Example

Server Code:

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

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    char buffer[BUFFER_SIZE];
    socklen_t client_len = sizeof(client_addr);

    // Create a socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        std::cerr << "Socket creation failed.\n";
        exit(EXIT_FAILURE);
    }

    // Bind the socket to an IP address and port
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        std::cerr << "Binding failed.\n";
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // Listen for incoming connections
    if (listen(server_fd, 5) == -1) {
        std::cerr << "Listening failed.\n";
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "Server listening on port " << PORT << "...\n";

    // Accept a client connection
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        std::cerr << "Connection acceptance failed.\n";
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "Client connected.\n";

    // Communicate with the client
    memset(buffer, 0, BUFFER_SIZE);
    read(client_fd, buffer, BUFFER_SIZE - 1);
    std::cout << "Client says: " << buffer << std::endl;

    std::string response = "Hello from server!";
    send(client_fd, response.c_str(), response.size(), 0);

    // Close the connection
    close(client_fd);
    close(server_fd);

    return 0;
}

Client Code:

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

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];

    // Create a socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        std::cerr << "Socket creation failed.\n";
        exit(EXIT_FAILURE);
    }

    // Define server address
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);

    // Convert IP address to binary form
    if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address/Address not supported.\n";
        close(sock);
        exit(EXIT_FAILURE);
    }

    // Connect to the server
    if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        std::cerr << "Connection failed.\n";
        close(sock);
        exit(EXIT_FAILURE);
    }
    std::cout << "Connected to the server.\n";

    // Communicate with the server
    std::string message = "Hello from client!";
    send(sock, message.c_str(), message.size(), 0);

    memset(buffer, 0, BUFFER_SIZE);
    read(sock, buffer, BUFFER_SIZE - 1);
    std::cout << "Server says: " << buffer << std::endl;

    // Close the connection
    close(sock);

    return 0;
}

Compiling and Running

  1. Compiling the server and client:

    g++ server.cpp -o server
    g++ client.cpp -o client
    
  2. Run the server:

    ./server
    
  3. In a new terminal, run the client:

    ./client
  4. You should see the exchange of messages between the server and the client.

Explanation

  1. Server:
    • The server listens on port 8080.
    • Accepts a connection from the client.
    • Reads a message from the client and sends a response back.
  2. Client:
    • The client connects to the server on 127.0.0.1:8080.
    • Sends a message to the server and prints the server’s response.