Imagine a TCP server and client. It’s not just about sending data; it’s about guaranteeing delivery, order, and that the data arrives without corruption.
Let’s watch a simple interaction. We have a Python client trying to connect to a server that’s just listening on port 8080.
Server (Python):
import socket
HOST = '127.0.0.1'
PORT = 8080
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
print(f"Server listening on {HOST}:{PORT}")
conn, addr = s.accept()
with conn:
print(f"Connected by {addr}")
while True:
data = conn.recv(1024)
if not data:
break
print(f"Received: {data.decode()}")
conn.sendall(b'Message received!')
Client (Python):
import socket
HOST = '127.0.0.1'
PORT = 8080
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(b'Hello, server!')
data = s.recv(1024)
print(f"Received from server: {data.decode()}")
When you run the server first, then the client, you’ll see the client’s "Hello, server!" printed on the server side, and the server’s "Message received!" printed on the client side. This back-and-forth, with acknowledgments, is the core of TCP’s reliability.
The fundamental problem TCP solves is the unreliable nature of underlying networks (like the internet, which is built on IP). IP is like sending postcards – they might get lost, arrive out of order, or be duplicated. TCP adds a layer of intelligence on top of IP to fix this. It establishes a persistent connection, breaks data into segments, numbers them, and ensures they are reassembled correctly at the other end. If a segment is lost, TCP detects it (via missing sequence numbers or timeouts) and retransmits it.
You control TCP’s behavior through socket options and the way you structure your send/receive logic. For instance, socket.SO_KEEPALIVE on the server can prevent the connection from being silently dropped by firewalls or idle network devices. When you sendall, TCP handles breaking that large chunk of data into smaller packets, managing congestion, and retransmitting until it gets an acknowledgment. On the receiving end, recv will block until data is available or the connection is closed, and TCP guarantees that the data it returns is in the correct order and free of errors.
A common misconception is that send() is equivalent to sendall(). send() might not send all the data you give it in a single call, especially if the network buffer is full. It returns the number of bytes actually sent. sendall(), on the other hand, keeps calling send() in a loop until all data is sent or an error occurs, making it the preferred choice for reliable transmission of complete messages.
The next logical step is understanding how to implement robust error handling and graceful connection termination in your TCP applications.