TCP echo: The three syscalls

Every TCP server in every language is the same three syscalls. socket() to create the file descriptor. bind() + listen() to claim the port. accept() to wait for a client. Once you can echo, you can do everything.

01-tcp-echo/server.py
python
import socket

HOST = "127.0.0.1"
PORT = 6380


def handle_client(client, addr):
    with client:
        while True:
            data = client.recv(4096)
            if not data:
                break
            client.sendall(data)


def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((HOST, PORT))
        server.listen(1)
        while True:
            client, addr = server.accept()
            handle_client(client, addr)

The whole server. SOCK_STREAM is the constant that says TCP. SO_REUSEADDR makes restart-edit-restart loops painless.

Three concepts you have to internalise. recv(4096) blocks until SOMETHING arrives, possibly less than 4096 bytes. sendall() loops internally until every byte is written. The empty-bytes return from recv means the client closed the connection cleanly.

handle_client runs in the main thread. While it is reading or echoing for one client, the next accept() never runs. The kernel queues the next connection (backlog of 1 in listen(1)) but anything past that gets refused. We replace this with the event loop at step 9.

terminal
bash
make 01-tcp-echo
# In another terminal:
nc localhost 6380
> hello
< hello

Run the echo server, then talk to it from netcat.

Quiz: Quiz

Loading practice…

AI prompt: Try it: write a Python client

Loading practice…