TCP sockets: a loopback echo server
Open a listening socket, connect a client, and exchange bytes over a reliable stream.
import socket
import threading
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 0))
server.listen(1)
port = server.getsockname()[1]
def serve():
connection, _ = server.accept()
with connection:
data = connection.recv(1024)
connection.sendall(data.upper())
thread = threading.Thread(target=serve)
thread.start()
with socket.create_connection(("127.0.0.1", port)) as client:
client.sendall(b"hello tcp")
reply = client.recv(1024)
thread.join()
server.close()
print(port > 0)
print(reply.decode())True HELLO TCP
Explanation
TCP provides a reliable, ordered, byte-stream connection between two processes. A server creates a socket, binds it to an address and port, and listens. A client connects, and the server accepts the connection and gets a new socket for that client. Both sides then send and receive bytes until one of them closes.
The program runs both ends in one process using the loopback address 127.0.0.1, so no traffic leaves the machine. Asking the operating system to bind port 0 picks a free port automatically; getsockname() reports which one. The server thread uppercases what it receives and sends it back, and the client prints the reply.
TCP is a stream, not a sequence of messages. One send can arrive split across several recv calls, and two sends can arrive combined. Real protocols therefore add framing, such as a length prefix or a delimiter, and loop until a full message has been read. The single small message here arrives in one piece, but code should not depend on that.
Explore the dry run
import socketimport threading server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind(("127.0.0.1", 0))server.listen(1)port = server.getsockname()[1] def serve(): connection, _ = server.accept() with connection: data = connection.recv(1024) connection.sendall(data.upper()) thread = threading.Thread(target=serve)thread.start()with socket.create_connection(("127.0.0.1", port)) as client: client.sendall(b"hello tcp") reply = client.recv(1024)thread.join()server.close()print(port > 0)print(reply.decode())Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Bind to the loopback address; port 0 asks the OS for any free port. | OS chooses a free port | — | |
| Start listening, with room for one pending connection. | OS chooses a free port | — | |
| The server thread waits until a client connects, then gets a new socket just for that client. | accept() returns the connection | — | |
| The client sends the bytes b'hello tcp' over the established connection. | accept() returns the connection | — | |
| The server sent back the uppercase bytes, decoded here into text. | accept() returns the connection | True HELLO TCP |
Cost and common mistakes
Setup is O(1). Transferring n bytes takes O(n) time; TCP handles retransmission and ordering for you.
- recv(n) returns up to n bytes, not necessarily a whole message. Loop until you have read a complete, framed message.
- Sockets carry bytes. Encode text with .encode() and decode received data with .decode().
- Close sockets, or use with blocks, to release the port and file descriptor. A server that never closes leaks resources.
Try it yourself
Why does the program bind to port 0 instead of a fixed port such as 8080?
Reveal answer and reasoning
A fixed port could already be in use on the machine. Port 0 lets the operating system pick a free port, which getsockname() then reports.