Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sockets

Overview

Sockets are the most versatile IPC mechanism — they work across different machines over a network, as well as between processes on the same machine. Sockets provide a bidirectional, reliable (TCP) or unreliable (UDP) communication channel.

Interview one-liner: “Sockets are network-capable IPC endpoints that support bidirectional communication — they work both locally (Unix domain) and across networks (TCP/UDP).”

Socket Types

TypeProtocolReliabilityConnectionUse Case
Stream (SOCK_STREAM)TCPReliable, orderedConnection-orientedWeb, SSH, databases
Datagram (SOCK_DGRAM)UDPUnreliable, unorderedConnectionlessDNS, gaming, video
Raw (SOCK_RAW)IPManualManualPacket crafting, ping
Unix Domain (SOCK_STREAM/_DGRAM)NoneReliableLocalLocal IPC

TCP Socket Example

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>

int main() {
    int server_fd, client_fd;
    struct sockaddr_in addr;
    char buffer[1024];
    
    // 1. Create socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    // 2. Set SO_REUSEADDR
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    // 3. Bind
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(8080);
    bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
    
    // 4. Listen
    listen(server_fd, 5);
    printf("Server listening on port 8080\n");
    
    // 5. Accept
    socklen_t addr_len = sizeof(addr);
    client_fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len);
    printf("Client connected: %s:%d\n", 
           inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
    
    // 6. Read/Write
    int n = read(client_fd, buffer, sizeof(buffer));
    write(client_fd, "Hello from server!", 18);
    
    // 7. Cleanup
    close(client_fd);
    close(server_fd);
    return 0;
}

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>

int main() {
    int sockfd;
    struct sockaddr_in addr;
    char buffer[1024];
    
    // 1. Create socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    // 2. Connect
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
    connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    
    // 3. Send/Receive
    write(sockfd, "Hello from client!", 18);
    int n = read(sockfd, buffer, sizeof(buffer));
    buffer[n] = '\0';
    printf("Server says: %s\n", buffer);
    
    // 4. Cleanup
    close(sockfd);
    return 0;
}

Unix Domain Sockets

For local IPC (same machine), Unix domain sockets are faster than TCP (no network stack overhead):

#include <sys/un.h>

// Server
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/mysocket");
unlink("/tmp/mysocket");
bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
listen(server_fd, 5);

// Client
int client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/mysocket");
connect(client_fd, (struct sockaddr *)&addr, sizeof(addr));

Socket System Calls Flow

sequenceDiagram
    participant Server
    participant Client
    
    Server->>Server: socket()
    Server->>Server: bind()
    Server->>Server: listen()
    
    Client->>Client: socket()
    Client->>Server: connect()
    Server->>Server: accept()
    
    Note over Server,Client: Connection established
    
    Client->>Server: write() / send()
    Server->>Server: read() / recv()
    Server->>Client: write() / send()
    Client->>Client: read() / recv()
    
    Client->>Client: close()
    Server->>Server: close()

UDP Socket Example

// Sender (UDP)
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr);

sendto(sockfd, "Hello UDP", 9, 0, 
       (struct sockaddr *)&dest, sizeof(dest));

// Receiver (UDP)
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8080);
bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));

char buffer[1024];
struct sockaddr_in sender;
socklen_t sender_len = sizeof(sender);
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                 (struct sockaddr *)&sender, &sender_len);

Performance Comparison

Socket TypeLatencyThroughputKernel Overhead
Unix Domain~2-5 μs~5-10 GB/sMinimal (no network stack)
TCP Loopback~10-30 μs~10-50 GbpsFull TCP stack
UDP Loopback~5-15 μs~10-50 GbpsUDP stack
TCP over LAN~50-200 μs~1-10 GbpsFull network stack

Interview Questions

Beginner

Q1: What is a socket?
A: A socket is an endpoint for communication. It’s an abstraction that allows processes to send and receive data, either locally (Unix domain) or over a network (TCP/UDP).

Q2: What is the difference between TCP and UDP sockets?
A: TCP (SOCK_STREAM): reliable, ordered, connection-oriented, error-checked. UDP (SOCK_DGRAM): unreliable, unordered, connectionless, lightweight. TCP for accuracy (web, email); UDP for speed (gaming, video streaming).

Intermediate

Q3: What is SO_REUSEADDR and why is it needed?
A: Without SO_REUSEADDR, a recently closed socket’s address enters TIME_WAIT state (~60s), preventing immediate rebinding. This is problematic for servers that restart. Setting SO_REUSEADDR allows binding to an address in TIME_WAIT.

Q4: How do Unix domain sockets compare to TCP sockets for local IPC?
A: Unix domain sockets: no network stack overhead, file-system based addressing, can pass file descriptors and credentials (SCM_RIGHTS), ~2-5μs latency. TCP loopback: full TCP stack overhead, IP-based addressing, ~10-30μs latency. Unix sockets are 3-5x faster for local IPC.

Q5: Explain the listen() backlog parameter.
A: listen(sockfd, backlog) sets the maximum length of the pending connection queue. When the server hasn’t called accept() fast enough, new connections queue here. If the queue is full, new connections receive ECONNREFUSED (TCP) or are silently dropped (implementation-dependent). Modern Linux: backlog is capped by /proc/sys/net/core/somaxconn (default 4096).

FAANG-Level

Q6: Design a TCP server handling 1 million concurrent connections.
A: 1) Use epoll (Linux) or io_uring for async I/O — avoid thread-per-connection, 2) SO_REUSEPORT for multiple listener sockets across CPUs, 3) Non-blocking sockets with edge-triggered epoll, 4) Per-CPU event loops with CPU affinity, 5) Minimize per-connection state (use connection pools), 6) Tune kernel: net.core.somaxconn, net.ipv4.tcp_max_syn_backlog, fs.file-max, 7) Memory: use slab allocators for connection objects, 8) Consider eBPF/XDP for packet filtering in kernel.

Q7: How does sendfile() work and why is it faster than read()+write()?
A: sendfile() transfers data from a file descriptor to a socket directly in kernel space, avoiding the copy to user space. Normal: read(file, user_buf) → copy 1, write(socket, user_buf) → copy 2. sendfile(): kernel copies directly from page cache to socket buffer — zero-copy from user perspective. Used by Nginx, Apache for static file serving. Modern Linux also supports splice() for pipe-based zero-copy.

Q8: Explain TCP connection establishment and teardown at the socket API level.
A: Establishment (3-way handshake): Client calls connect() → SYN sent → SYN-ACK received → ACK sent → connect() returns. Server: listen()accept() blocks → SYN received → SYN-ACK sent → ACK received → accept() returns new fd. Teardown (4-way): Active close: close() → FIN sent → ACK received → wait for remote FIN → ACK sent → TIME_WAIT (2*MSL). Passive close: FIN received → read() returns 0 → ACK sent → close() → FIN sent → ACK received.

Common Mistakes

  1. Not handling partial reads/writes: TCP is a byte stream — read() may return less than requested. Always loop until all data is received.
  2. Forgetting SO_REUSEADDR: Server restart fails with “Address already in use”.
  3. Using TCP for simple request-response: If you don’t need reliability, UDP is simpler and faster.
  4. Not closing sockets: Leads to file descriptor leaks and port exhaustion.
  5. Ignoring SIGPIPE: Writing to a closed connection generates SIGPIPE. Use send() with MSG_NOSIGNAL or handle the signal.

Summary

Socket TypeUse CaseConnectionReliabilitySpeed
TCPWeb, databases, file transferConnection-orientedReliable, orderedMedium
UDPGaming, video, DNSConnectionlessUnreliableFast
Unix DomainLocal IPCBothReliableFastest

Cross-References

Cross References