CNetworking

C TCP/IP Socket Programming: Build Network Servers and Clients from Scratch

TT
TopicTrick Team
C TCP/IP Socket Programming: Build Network Servers and Clients from Scratch

C TCP/IP Socket Programming: Build Network Servers and Clients from Scratch


Table of Contents


The Network Stack: How TCP Fits In

mermaid

The C socket API operates at the TCP/UDP layer. You don't manage IP routing or Ethernet framing — the OS kernel handles those. You deal with: connect endpoints (IP + port), send/receive byte streams (TCP), and file descriptor management.


The Socket Connection Lifecycle

mermaid

Creating a TCP Server: Step by Step

c

Creating a TCP Client

c

Handling Multiple Clients: select() and poll()

The simple server above handles one client at a time. For multiple concurrent clients, use select() or poll():

c

[!NOTE] select() has a limit of FD_SETSIZE (typically 1024) file descriptors. For production servers handling thousands of connections, use epoll (Linux) or kqueue (macOS/BSD).


Non-Blocking Sockets and epoll (Linux)

For high-performance servers (10,000+ concurrent connections), epoll is the standard:

c

epoll with edge-triggered mode (EPOLLET) is the foundation of Nginx's event loop and the mechanism enabling its legendary 10,000+ concurrent connection handling.


Sending and Receiving Complete Messages

send() and recv() may not send/receive all bytes in one call for large messages. Production code must loop:

c

Socket Options: SO_REUSEADDR and TCP_NODELAY

c

Frequently Asked Questions

Why do I need htons() for port numbers? Different CPU architectures store multi-byte numbers in different byte orders. x86-64 is little-endian; the internet uses big-endian (network byte order). htons() (host-to-network short) converts a 16-bit port number from your CPU's format to big-endian. Always use htons() for ports and htonl() for IP addresses.

What is the difference between send/recv and write/read on sockets? On POSIX, write(fd, buf, n) and send(fd, buf, n, 0) are functionally identical for TCP sockets (with flags=0). send/recv provide the additional flags parameter for features like MSG_DONTWAIT (non-blocking) and MSG_PEEK (peek without consuming).

How does Nginx handle 10,000 concurrent connections? Nginx uses a single-threaded event loop with epoll in edge-triggered mode. All sockets are non-blocking. When a socket has data, epoll notifies the event loop; the handler reads what's available and returns immediately (no blocking). One thread serves all connections through multiplexing.

What is the TIME_WAIT state? After a TCP connection closes, the OS keeps the (source_ip, source_port, dest_ip, dest_port) tuple in TIME_WAIT for 2× Maximum Segment Lifetime (~60-120 seconds). This prevents old delayed packets from a previous connection being misinterpreted as belonging to a new connection on the same ports. SO_REUSEADDR bypasses this for the server's listening socket.


Key Takeaway

TCP socket programming is the Gateway to Networked Systems. The six-step server lifecycle (socket → bind → listen → accept → recv/send → close) is the template behind every web server, database network protocol, and distributed system message passing system ever written in C.

Once you understand blocking I/O with select/poll and non-blocking I/O with epoll, you have the foundation to understand (and contribute to) the Nginx, Redis, and PostgreSQL networking code at its core.

Read next: UDP Datagrams & Broadcast: Fast, Low-Latency Networking →


Part of the C Mastery Course — 30 modules from C basics to expert networking and systems engineering.