Network Programming in C

Network Programming in C

Objectives

  • Understand socket API fundamentals for TCP and UDP
  • Implement client-server communication patterns
  • Learn I/O multiplexing with select/poll for concurrent connections
  • Handle network byte order and address conversion

Difficulty: ⭐⭐⭐⭐ (Advanced)


Table of Contents

  1. Socket Fundamentals
  2. TCP Communication
  3. UDP Communication
  4. I/O Multiplexing
  5. Practical Patterns
  6. Practice Problems
  7. References

1. Socket Fundamentals

1.1 What is a Socket?

A socket is an endpoint for network communication. It combines an IP address and a port number to identify a specific process on a specific machine.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Socket Communication                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                            β”‚
β”‚  [Client Machine]              [Server Machine]            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚  Application β”‚              β”‚  Application β”‚            β”‚
β”‚  β”‚    Process   β”‚              β”‚    Process   β”‚            β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚              β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚            β”‚
β”‚  β”‚  β”‚ Socket β”‚  β”‚   Network    β”‚  β”‚ Socket β”‚  β”‚            β”‚
β”‚  β”‚  β”‚  fd=3  │◀─┼─────────────┼─▢│  fd=4  β”‚  β”‚            β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚              β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚            β”‚
β”‚  β”‚ 192.168.1.10 β”‚              β”‚ 192.168.1.20 β”‚            β”‚
β”‚  β”‚   :54321     β”‚              β”‚   :8080      β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚                                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1.2 Socket API Overview

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

// Key functions:
// socket()   - Create a socket
// bind()     - Bind socket to address
// listen()   - Mark socket as passive (server)
// accept()   - Accept incoming connection
// connect()  - Initiate connection (client)
// send/recv  - Data transfer (TCP)
// sendto/recvfrom - Data transfer (UDP)
// close()    - Close socket

1.3 Address Structures

// IPv4 address structure
struct sockaddr_in {
    sa_family_t    sin_family;   // AF_INET
    in_port_t      sin_port;     // Port (network byte order)
    struct in_addr sin_addr;     // IPv4 address
};

// Generic address structure (used in API)
struct sockaddr {
    sa_family_t sa_family;
    char        sa_data[14];
};

1.4 Byte Order Conversion

Network protocols use big-endian (network byte order), but most modern CPUs use little-endian.

#include <arpa/inet.h>

uint16_t port = 8080;

// Host to Network
uint16_t net_port = htons(port);     // host to network short
uint32_t net_addr = htonl(INADDR_ANY); // host to network long

// Network to Host
uint16_t host_port = ntohs(net_port);  // network to host short
uint32_t host_addr = ntohl(net_addr);  // network to host long

// Address conversion
const char *ip_str = "192.168.1.10";
struct in_addr addr;
inet_pton(AF_INET, ip_str, &addr);  // String β†’ binary

char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr, buf, sizeof(buf));  // Binary β†’ string
printf("Address: %s\n", buf);  // "192.168.1.10"

2. TCP Communication

2.1 TCP Client-Server Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Server                              Client              β”‚
β”‚  ──────                              ──────              β”‚
β”‚  socket()                            socket()            β”‚
β”‚     β”‚                                   β”‚                β”‚
β”‚  bind()                                 β”‚                β”‚
β”‚     β”‚                                   β”‚                β”‚
β”‚  listen()                               β”‚                β”‚
β”‚     β”‚                                   β”‚                β”‚
β”‚  accept() ◀── 3-way handshake ──── connect()            β”‚
β”‚     β”‚                                   β”‚                β”‚
β”‚  recv() ◀──────── data ────────── send()                β”‚
β”‚     β”‚                                   β”‚                β”‚
β”‚  send() ────────── data ──────────▢ recv()              β”‚
β”‚     β”‚                                   β”‚                β”‚
β”‚  close() ◀── 4-way teardown ───── close()              β”‚
β”‚                                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2.2 TCP Echo Server

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

#define PORT 8080
#define BUF_SIZE 1024

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

    // 1. Create socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // Allow address reuse (avoid "Address already in use")
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 2. Bind to address
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;  // All interfaces
    server_addr.sin_port = htons(PORT);

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

    // 3. Listen for connections
    if (listen(server_fd, 5) < 0) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);

    // 4. Accept and handle clients
    while (1) {
        client_fd = accept(server_fd, (struct sockaddr *)&client_addr,
                           &client_len);
        if (client_fd < 0) {
            perror("accept");
            continue;
        }

        char client_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip,
                  sizeof(client_ip));
        printf("Client connected: %s:%d\n", client_ip,
               ntohs(client_addr.sin_port));

        // Echo loop
        ssize_t bytes;
        while ((bytes = recv(client_fd, buffer, BUF_SIZE - 1, 0)) > 0) {
            buffer[bytes] = '\0';
            printf("Received: %s", buffer);
            send(client_fd, buffer, bytes, 0);
        }

        printf("Client disconnected\n");
        close(client_fd);
    }

    close(server_fd);
    return 0;
}

2.3 TCP Echo Client

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

#define PORT 8080
#define BUF_SIZE 1024

int main(int argc, char *argv[]) {
    const char *server_ip = (argc > 1) ? argv[1] : "127.0.0.1";

    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);

    if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) {
        fprintf(stderr, "Invalid address: %s\n", server_ip);
        close(sock_fd);
        exit(EXIT_FAILURE);
    }

    if (connect(sock_fd, (struct sockaddr *)&server_addr,
                sizeof(server_addr)) < 0) {
        perror("connect");
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    printf("Connected to %s:%d\n", server_ip, PORT);

    char buffer[BUF_SIZE];
    while (fgets(buffer, BUF_SIZE, stdin) != NULL) {
        send(sock_fd, buffer, strlen(buffer), 0);

        ssize_t bytes = recv(sock_fd, buffer, BUF_SIZE - 1, 0);
        if (bytes <= 0) break;
        buffer[bytes] = '\0';
        printf("Echo: %s", buffer);
    }

    close(sock_fd);
    return 0;
}

2.4 Handling Partial Reads/Writes

TCP is a stream protocol. send() and recv() may transfer fewer bytes than requested.

// Robust send: ensure all bytes are sent
ssize_t send_all(int fd, const void *buf, size_t len) {
    const char *p = buf;
    size_t remaining = len;

    while (remaining > 0) {
        ssize_t sent = send(fd, p, remaining, 0);
        if (sent < 0) return -1;
        if (sent == 0) return len - remaining;
        p += sent;
        remaining -= sent;
    }
    return len;
}

// Robust recv: read exactly n bytes
ssize_t recv_exact(int fd, void *buf, size_t len) {
    char *p = buf;
    size_t remaining = len;

    while (remaining > 0) {
        ssize_t received = recv(fd, p, remaining, 0);
        if (received < 0) return -1;
        if (received == 0) return len - remaining;  // Connection closed
        p += received;
        remaining -= received;
    }
    return len;
}

3. UDP Communication

3.1 UDP vs TCP

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Feature          β”‚  TCP             β”‚  UDP              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Connection       β”‚  Connection-     β”‚  Connectionless   β”‚
β”‚                   β”‚  oriented        β”‚                   β”‚
β”‚  Reliability      β”‚  Guaranteed      β”‚  Best-effort      β”‚
β”‚  Ordering         β”‚  Preserved       β”‚  Not guaranteed   β”‚
β”‚  Flow Control     β”‚  Yes             β”‚  No               β”‚
β”‚  Overhead         β”‚  Higher          β”‚  Lower            β”‚
β”‚  Use Cases        β”‚  HTTP, SSH,      β”‚  DNS, VoIP,       β”‚
β”‚                   β”‚  file transfer   β”‚  gaming, streamingβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3.2 UDP Sender and Receiver

// --- UDP Receiver (Server) ---
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 9090
#define BUF_SIZE 1024

int main(void) {
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    if (bind(sock_fd, (struct sockaddr *)&server_addr,
             sizeof(server_addr)) < 0) {
        perror("bind");
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    printf("UDP receiver listening on port %d...\n", PORT);

    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[BUF_SIZE];

    while (1) {
        ssize_t bytes = recvfrom(sock_fd, buffer, BUF_SIZE - 1, 0,
                                 (struct sockaddr *)&client_addr,
                                 &client_len);
        if (bytes < 0) {
            perror("recvfrom");
            continue;
        }
        buffer[bytes] = '\0';

        char client_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip,
                  sizeof(client_ip));
        printf("[%s:%d] %s", client_ip,
               ntohs(client_addr.sin_port), buffer);

        // Echo back
        sendto(sock_fd, buffer, bytes, 0,
               (struct sockaddr *)&client_addr, client_len);
    }

    close(sock_fd);
    return 0;
}
// --- UDP Sender (Client) ---
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 9090
#define BUF_SIZE 1024

int main(int argc, char *argv[]) {
    const char *server_ip = (argc > 1) ? argv[1] : "127.0.0.1";

    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, server_ip, &server_addr.sin_addr);

    char buffer[BUF_SIZE];
    while (fgets(buffer, BUF_SIZE, stdin) != NULL) {
        sendto(sock_fd, buffer, strlen(buffer), 0,
               (struct sockaddr *)&server_addr, sizeof(server_addr));

        struct sockaddr_in from_addr;
        socklen_t from_len = sizeof(from_addr);
        ssize_t bytes = recvfrom(sock_fd, buffer, BUF_SIZE - 1, 0,
                                 (struct sockaddr *)&from_addr,
                                 &from_len);
        if (bytes > 0) {
            buffer[bytes] = '\0';
            printf("Echo: %s", buffer);
        }
    }

    close(sock_fd);
    return 0;
}

4. I/O Multiplexing

4.1 Why Multiplexing?

A simple server using accept() in a loop can only handle one client at a time. I/O multiplexing lets a single thread monitor multiple file descriptors.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   I/O Multiplexing                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                              β”‚
β”‚  β”‚ Client 1 │──┐                                           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”œβ”€β”€β”€β”€β–Άβ”‚ select/poll/ │───▢│   Server     β”‚ β”‚
β”‚  β”‚ Client 2 │───     β”‚    epoll     β”‚    β”‚   Handler    β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚     "Which fd is ready?"                  β”‚
β”‚  β”‚ Client 3 β”‚β”€β”€β”˜                                           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                              β”‚
β”‚                                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

4.2 select()

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define MAX_CLIENTS 10
#define BUF_SIZE 1024

int main(void) {
    int server_fd, client_fds[MAX_CLIENTS];
    fd_set read_fds, active_fds;
    int max_fd;

    // Initialize client array
    for (int i = 0; i < MAX_CLIENTS; i++)
        client_fds[i] = -1;

    // Create and setup server socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_addr.s_addr = INADDR_ANY,
        .sin_port = htons(PORT)
    };
    bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
    listen(server_fd, 5);
    printf("Select server on port %d\n", PORT);

    FD_ZERO(&active_fds);
    FD_SET(server_fd, &active_fds);
    max_fd = server_fd;

    char buffer[BUF_SIZE];

    while (1) {
        read_fds = active_fds;  // select modifies the set

        int ready = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (ready < 0) {
            perror("select");
            break;
        }

        // Check server socket for new connections
        if (FD_ISSET(server_fd, &read_fds)) {
            struct sockaddr_in client_addr;
            socklen_t len = sizeof(client_addr);
            int new_fd = accept(server_fd,
                               (struct sockaddr *)&client_addr, &len);
            if (new_fd >= 0) {
                // Add to client list
                for (int i = 0; i < MAX_CLIENTS; i++) {
                    if (client_fds[i] == -1) {
                        client_fds[i] = new_fd;
                        FD_SET(new_fd, &active_fds);
                        if (new_fd > max_fd) max_fd = new_fd;
                        printf("New client connected (fd=%d)\n", new_fd);
                        break;
                    }
                }
            }
        }

        // Check client sockets for data
        for (int i = 0; i < MAX_CLIENTS; i++) {
            int fd = client_fds[i];
            if (fd == -1) continue;

            if (FD_ISSET(fd, &read_fds)) {
                ssize_t bytes = recv(fd, buffer, BUF_SIZE - 1, 0);
                if (bytes <= 0) {
                    // Client disconnected
                    printf("Client disconnected (fd=%d)\n", fd);
                    close(fd);
                    FD_CLR(fd, &active_fds);
                    client_fds[i] = -1;
                } else {
                    buffer[bytes] = '\0';
                    // Echo to all clients
                    for (int j = 0; j < MAX_CLIENTS; j++) {
                        if (client_fds[j] != -1) {
                            send(client_fds[j], buffer, bytes, 0);
                        }
                    }
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

4.3 poll()

poll() removes the FD_SETSIZE limit of select() and provides a cleaner interface.

#include <poll.h>

#define MAX_FDS 100

struct pollfd fds[MAX_FDS];
int nfds = 1;

// Setup server socket
fds[0].fd = server_fd;
fds[0].events = POLLIN;

while (1) {
    int ready = poll(fds, nfds, -1);  // -1 = block indefinitely
    if (ready < 0) {
        perror("poll");
        break;
    }

    // New connection?
    if (fds[0].revents & POLLIN) {
        int new_fd = accept(server_fd, NULL, NULL);
        if (new_fd >= 0 && nfds < MAX_FDS) {
            fds[nfds].fd = new_fd;
            fds[nfds].events = POLLIN;
            nfds++;
        }
    }

    // Check existing clients
    for (int i = 1; i < nfds; i++) {
        if (fds[i].revents & POLLIN) {
            char buf[1024];
            ssize_t n = recv(fds[i].fd, buf, sizeof(buf), 0);
            if (n <= 0) {
                close(fds[i].fd);
                fds[i] = fds[nfds - 1];  // Remove by swapping
                nfds--;
                i--;
            } else {
                send(fds[i].fd, buf, n, 0);  // Echo
            }
        }
    }
}

4.4 Comparison: select vs poll vs epoll

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              β”‚  select        β”‚  poll        β”‚  epoll        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Max FDs      β”‚ FD_SETSIZE     β”‚ Unlimited    β”‚ Unlimited     β”‚
β”‚              β”‚ (usually 1024) β”‚              β”‚               β”‚
β”‚ Complexity   β”‚ O(n)           β”‚ O(n)         β”‚ O(1) amortizedβ”‚
β”‚ Portability  β”‚ POSIX          β”‚ POSIX        β”‚ Linux only    β”‚
β”‚ Overhead     β”‚ Copy fd_set    β”‚ Copy array   β”‚ Kernel-managedβ”‚
β”‚              β”‚ each call      β”‚ each call    β”‚               β”‚
β”‚ Best for     β”‚ Small # fds    β”‚ Moderate fds β”‚ Thousands fds β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

5. Practical Patterns

5.1 Message Framing with Length Prefix

TCP is a byte stream. To send discrete messages, use a length prefix.

#include <stdint.h>

// Send a length-prefixed message
int send_message(int fd, const char *msg, uint32_t len) {
    uint32_t net_len = htonl(len);
    if (send_all(fd, &net_len, sizeof(net_len)) < 0) return -1;
    if (send_all(fd, msg, len) < 0) return -1;
    return 0;
}

// Receive a length-prefixed message
int recv_message(int fd, char *buf, uint32_t buf_size, uint32_t *out_len) {
    uint32_t net_len;
    if (recv_exact(fd, &net_len, sizeof(net_len)) <= 0) return -1;

    uint32_t len = ntohl(net_len);
    if (len > buf_size - 1) return -1;  // Message too large

    if (recv_exact(fd, buf, len) <= 0) return -1;
    buf[len] = '\0';
    *out_len = len;
    return 0;
}

5.2 Non-blocking Socket

#include <fcntl.h>
#include <errno.h>

// Set socket to non-blocking mode
int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

// Non-blocking recv check
ssize_t bytes = recv(fd, buffer, sizeof(buffer), 0);
if (bytes < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
        // No data available right now - not an error
    } else {
        perror("recv");
    }
}

5.3 Graceful Shutdown

// Graceful shutdown: signal that no more data will be sent
shutdown(client_fd, SHUT_WR);  // Close write direction

// Then drain remaining data from the other side
char drain[256];
while (recv(client_fd, drain, sizeof(drain), 0) > 0)
    ;

close(client_fd);

6. Practice Problems

Problem 1: Multi-client Chat Server

Build a chat server where messages from one client are broadcast to all connected clients. Use select() or poll() for multiplexing.

Requirements: - Support at least 10 simultaneous clients - Display "[username] message" format - Handle client disconnection gracefully

Problem 2: File Transfer

Implement a simple file transfer protocol: - Client sends a filename, server responds with the file contents - Use length-prefix framing for messages - Handle file-not-found errors

Problem 3: HTTP Client

Write a minimal HTTP/1.1 client that: - Connects to a web server on port 80 - Sends a GET request - Parses and displays the response headers and body


7. References

  • W. Richard Stevens, Unix Network Programming, Volume 1 (3rd ed.)
  • Beej's Guide to Network Programming: https://beej.us/guide/bgnet/
  • man 2 socket, man 2 bind, man 2 select, man 2 poll

Previous: 20_Advanced_Pointers | Next: 22_IPC_and_Signals

to navigate between lessons