
Transmission Control Protocol (TCP) is a cornerstone of internet communications, ensuring that data sent over the network arrives intact and in the correct order. Without TCP, the internet as we know it would be chaotic; packets of data could arrive out of sequence or not at all, leading to corrupted files and a frustrating user experience.
TCP operates on a connection-oriented model, meaning that a connection is established between the client and server before data transmission begins. This very important for applications that rely on data integrity, such as web browsing, file transfers, and email. The reliability of TCP is achieved through mechanisms like error checking, retransmission of lost packets, and flow control, making it a robust choice for many networking applications.
Each TCP connection is uniquely identified by a combination of the source IP address, source port, destination IP address, and destination port. This ensures that data sent from one application can be correctly routed to its intended destination, even when multiple applications are using the network at once.
To illustrate the importance of TCP, consider a scenario where you are downloading a large file. If the connection drops halfway through the download, TCP will recognize the loss and automatically request the missing data packets to ensure that you receive the complete file. This level of reliability is what sets TCP apart from other protocols like UDP, which does not guarantee delivery and is often used in applications like video streaming where speed is prioritized over perfect accuracy.
Understanding how TCP works is essential for any programmer working with networked applications. It provides a solid foundation for building reliable client-server architectures. The next step is to explore how we can implement TCP connections in Python, using its socket library to create our own client-server applications.
import socket
def create_server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 12345))
server_socket.listen(1)
print("Server is listening on port 12345...")
conn, addr = server_socket.accept()
print(f"Connection from {addr} has been established!")
conn.sendall(b'Hello, client!')
conn.close()
create_server()
This simple server listens for incoming connections on localhost at port 12345. Once a connection is established, it sends a greeting message to the client. Next, we can develop the client-side code that connects to this server and receives messages.
import socket
def create_client():
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 12345))
message = client_socket.recv(1024)
print(f"Received message: {message.decode()}")
client_socket.close()
create_client()
65W USB C Laptop Charger Compatible with Lenovo ThinkPad T480 T490 T580 X1 Carbon T14 T14s T16 E14 E16 Yoga C940 C930 720 910 920 7i Chromebook C330 S330,Type C Fast Charging Power Supply Adapter Cord
$17.99 (as of October 24, 2026 20:23 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Getting started with Python sockets
The client code connects to the server using the same port and address. Once connected, it waits to receive a message from the server. This basic interaction highlights the TCP connection’s reliability: the client can expect to receive data from the server without worrying about packet loss or out-of-order delivery.
Now that we have the basic client and server set up, it’s worth examining how to handle multiple clients at once. In real-world applications, servers often need to manage multiple connections at the same time. Python’s socket library allows us to do this by using threading or asynchronous I/O.
import socket
import threading
def handle_client(conn, addr):
print(f"New connection from {addr}")
conn.sendall(b'Hello, client!')
conn.close()
def create_server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 12345))
server_socket.listen(5)
print("Server is listening on port 12345...")
while True:
conn, addr = server_socket.accept()
client_thread = threading.Thread(target=handle_client, args=(conn, addr))
client_thread.start()
create_server()
In this version of the server, we spawn a new thread for each client connection. This allows the server to handle multiple clients concurrently without blocking. Each client connection is processed independently, which is important for performance in a real-world scenario.
On the client side, we can test the server by running multiple instances of the client code. Each instance will establish its own connection to the server, demonstrating how the server can manage multiple interactions concurrently.
import socket
import time
def create_client(client_id):
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 12345))
message = client_socket.recv(1024)
print(f"Client {client_id} received message: {message.decode()}")
client_socket.close()
for i in range(5):
threading.Thread(target=create_client, args=(i,)).start()
time.sleep(1) # Stagger client connections
This client code creates multiple threads, each representing a different client trying to connect to the server. The use of time.sleep(1) staggers the connections slightly, making it easier to observe how the server handles each connection as it comes in. With this approach, we can scale our server to handle a high number of clients efficiently.
Implementing a basic TCP client-server model
To further enhance our server’s capabilities, we can implement a simple protocol for the messages exchanged between the client and server. This will allow clients to send specific commands, and the server can respond accordingly. For instance, clients might request different types of data or services, and the server can handle these requests based on the command received.
import socket
import threading
def handle_client(conn, addr):
print(f"New connection from {addr}")
while True:
data = conn.recv(1024)
if not data:
break
command = data.decode().strip()
if command == "TIME":
response = f"The current server time is: {time.ctime()}"
else:
response = "Unknown command"
conn.sendall(response.encode())
conn.close()
def create_server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 12345))
server_socket.listen(5)
print("Server is listening on port 12345...")
while True:
conn, addr = server_socket.accept()
client_thread = threading.Thread(target=handle_client, args=(conn, addr))
client_thread.start()
create_server()
In this updated server code, the handle_client function processes incoming commands from the client. If the client sends the command “TIME”, the server responds with the current server time. If an unknown command is received, it sends back an error message. This simple command-response structure lays the groundwork for more complex interactions.
On the client side, we need to modify our client code to send commands to the server. Here’s how we can implement that:
import socket
def create_client(command):
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 12345))
client_socket.sendall(command.encode())
response = client_socket.recv(1024)
print(f"Received response: {response.decode()}")
client_socket.close()
commands = ["TIME", "UNKNOWN"]
for cmd in commands:
create_client(cmd)
This client code sends a command to the server and waits for a response. It demonstrates how clients can interact with the server using a predefined set of commands. The server’s ability to respond appropriately adds a layer of functionality that can be expanded upon.
As we develop our application further, we can introduce additional commands, such as “ECHO”, where the server simply returns the message sent by the client. This would serve as a useful test to ensure that the communication between client and server is functioning correctly.
def handle_client(conn, addr):
print(f"New connection from {addr}")
while True:
data = conn.recv(1024)
if not data:
break
command = data.decode().strip()
if command == "ECHO":
conn.sendall(data) # Echo back the received message
elif command == "TIME":
response = f"The current server time is: {time.ctime()}"
conn.sendall(response.encode())
else:
response = "Unknown command"
conn.sendall(response.encode())
conn.close()
This modification allows the server to echo back any message it receives when the “ECHO” command is sent. Handling commands like this makes the server more interactive and useful for various applications.
Moreover, consider adding error handling to manage scenarios such as network issues or unexpected client behavior. Robust error handling ensures that your server remains stable and can recover gracefully from problems.
try:
# Existing server code...
except Exception as e:
print(f"An error occurred: {e}")
conn.close()

