Class 30 of 37 • System Programming (C) \t\t\t\t

Linux Socket Programming in C

Network communication between different computers on the Internet is built on the Berkeley Sockets API. In Linux, true to the Unix philosophy that "Everything is a file", open network connections are represented and manipulated using standard integer File Descriptors. Socket programming enables two independent processes running on the same host or on computers separated across the world to communicate over TCP/IP or UDP networks.

Linux TCP Client-Server Socket Programming Flow

Figure 1: Complete execution lifecycle of a TCP Server and Client across connection handshake and payload exchange.

TCP Client-Server Workflow Breakdown

A reliable TCP connection requires a coordinated sequence of kernel system calls on both server and client:

Side

System Call

Role & Operation

Server & Client

socket()

Creates an unbound network communication endpoint and returns a file descriptor.

Server

bind()

Assigns a local IP address and port number (e.g. 8080) to the socket file descriptor.

Server

listen()

Puts socket into passive listening mode and defines maximum pending connection backlog queue.

Server

accept()

Blocking call: waits for incoming client handshake and returns a new socket descriptor for data exchange.

Client

connect()

Initiates 3-way TCP SYN/ACK handshake with the server's listening IP and port.

Both

read() / write()

Transmits and receives byte streams across the established socket file descriptor.

Both

close()

Terminates the TCP session via 4-way FIN/ACK handshake and releases file descriptors.

Socket Addressing: struct sockaddr_in & Byte Ordering

Internet addresses are stored in the struct sockaddr_in structure declared in <netinet/in.h>. Because different computer chips represent multibyte numbers differently (Intel/AMD use Little-Endian, while network headers strictly require Big-Endian), programmers must convert values using byte-ordering helper functions:

  • htons() : Host TO Network Short (converts 16-bit integer port number).
  • ntohs() : Network TO Host Short.
  • inet_pton() : Converts presentation IP string (e.g. "127.0.0.1") to binary network format.
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));

server_addr.sin_family = AF_INET;                // IPv4 Internet protocols
server_addr.sin_port = htons(8080);               // Port 8080 converted to Network Byte Order
server_addr.sin_addr.s_addr = INADDR_ANY;         // Bind to all available local network interfaces

Compilable Example 1: Robust TCP Server in C (server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
    socklen_t addr_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE] = {0};
    char response[] = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello from Veewom Linux Socket Server!";

    // 1. Create socket: AF_INET = IPv4, SOCK_STREAM = TCP
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // Optional: Allow instant socket address reuse to prevent "Address already in use" errors
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 2. Configure server address
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 3. Bind socket to IP & Port
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 4. Put socket in listening mode (max queue of 5 pending connections)
    if (listen(server_fd, 5) < 0) {
        perror("Listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("[SERVER] Listening on port %d... Waiting for incoming client.\n", PORT);

    // 5. Accept client connection (Blocks until client arrives)
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
    if (client_fd < 0) {
        perror("Accept failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
    printf("[SERVER] Connection accepted from %s:%d\n", client_ip, ntohs(client_addr.sin_port));

    // 6. Read client payload
    ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("[SERVER] Received from client: '%s'\n", buffer);
    }

    // 7. Send response
    write(client_fd, response, strlen(response));
    printf("[SERVER] Response transmitted. Closing client socket.\n");

    // 8. Clean up
    close(client_fd);
    close(server_fd);
    return 0;
}

Compilable Example 2: TCP Client in C (client.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock_fd;
    struct sockaddr_in serv_addr;
    char message[] = "Client Query: Requesting Linux programming data";
    char buffer[BUFFER_SIZE] = {0};

    // 1. Create client socket
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0) {
        perror("Socket creation failed");
        return 1;
    }

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

    // Convert IPv4 address from text to binary form (localhost)
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("Invalid address / Address not supported");
        return 2;
    }

    // 2. Connect to server
    printf("[CLIENT] Connecting to server at 127.0.0.1:%d...\n", PORT);
    if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection Failed");
        close(sock_fd);
        return 3;
    }

    printf("[CLIENT] Connected! Sending message...\n");

    // 3. Write data to server socket
    write(sock_fd, message, strlen(message));

    // 4. Read response from server
    ssize_t bytes_received = read(sock_fd, buffer, sizeof(buffer) - 1);
    if (bytes_received > 0) {
        buffer[bytes_received] = '\0';
        printf("[CLIENT] Server response received:\n%s\n", buffer);
    }

    // 5. Close connection
    close(sock_fd);
    return 0;
}

Testing the Client and Server Locally

# Terminal 1: Compile and run server
$ gcc server.c -o server
$ ./server

# Terminal 2: Compile and run client
$ gcc client.c -o client
$ ./client

Lab Practice: Concurrent Multi-Client Server Using fork()

Problem: An iterative server can only serve one client at a time. Write a concurrent TCP server that forks a child process for each incoming client to handle multiple simultaneous connections.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>

// Automatically reap zombie child processes
void sigchld_handler(int sig) {
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t sin_size = sizeof(cli_addr);

    // Prevent zombie children with SIGCHLD handler
    signal(SIGCHLD, sigchld_handler);

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(9090);

    bind(server_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    listen(server_fd, 10);
    printf("Concurrent Server listening on port 9090...\n");

    while (1) {
        client_fd = accept(server_fd, (struct sockaddr*)&cli_addr, &sin_size);
        if (client_fd < 0) continue;

        // Fork child process to handle connected client
        if (fork() == 0) {
            // --- CHILD PROCESS ---
            close(server_fd); // Child does not need listening socket
            char echo_buf[256];
            ssize_t n = read(client_fd, echo_buf, sizeof(echo_buf));
            write(client_fd, echo_buf, n); // Echo back
            close(client_fd);
            exit(0); // Child terminates safely
        }
        // --- PARENT PROCESS ---
        close(client_fd); // Parent does not need client socket; continues listening
    }

    close(server_fd);
    return 0;
}

Frequently Asked Questions (FAQs)

TCP (SOCK_STREAM) is connection-oriented, guaranteed, ordered, and error-checked, using handshakes and acknowledgments. UDP (SOCK_DGRAM) is connectionless, fast, and does not guarantee packet arrival or ordering.
When a server restarts, the operating system holds the port in a TIME_WAIT state for up to two minutes. Setting SO_REUSEADDR with setsockopt() allows the server to bind to the port immediately without waiting.
bind() is called by the server to associate an open socket with a well-known local port and IP address. connect() is called by the client to initiate an active connection with a remote server's address.
htons() stands for "Host TO Network Short". It converts a 16-bit short integer (such as a port number) from the host machine's native CPU byte order into the standardized Big-Endian Network Byte Order.

Next Step in Your Linux Learning Path:

Understand virtual memory layout, page tables, and memory-mapped files in Class 31: Virtual Memory & mmap().

Continue to Class 31: Linux Virtual Memory & mmap() → Previous: ← Pthreads | Full Syllabus

Share Share on Facebook Share on Twitter Share on LinkedIn Pin on Pinterest Share on Stumbleupon Share on Tumblr Share on Reddit Share on Diggit

You may also like this!