TCP Programming

Python Socket Getting Started and Practical Combat

TCP is a connection-oriented and reliable transport layer protocol that supports most Internet applications such as HTTP, SSH, and email. in the Python standard librarysocketThe module provides a set of clear, cross-platform network programming interfaces. Starting from the basic concepts, this article will take you step by step to write a modern style TCP client and server, and at the same time understand the simple usage of UDP, optimization techniques in the production environment, as well as common problems and countermeasures.


1. Three core elements of Socket

To establish effective network communication, a socket needs to determine three elements at the same time:

  1. Target host identification: IPv4/IPv6 address or domain name (such aswww.example.com)。
  2. Port number: an integer between 1 and 65535, used to distinguish different services on the same host (such as HTTP default 80, HTTPS 443).
  3. Transmission Protocol:
    • SOCK_STREAM:TCP, a connection-oriented streaming protocol, ensures the orderly and reliable arrival of data.
    • SOCK_DGRAM: UDP, a connectionless datagram protocol, does not guarantee reliability, but is more efficient in scenarios with high real-time requirements (such as live broadcasts and games).

2. TCP client programming

TCP establishes a connection through a "three-way handshake" and disconnects through a "four-way wave". Data transmission is like reading and writing files, it is an ordered byte stream.

The following example demonstrates how to use the underlying socket to send a simple HTTP request. This way of writing requires manual callingclose(), it is easy to forget to release resources due to exceptions, and is not recommended for use in production now.

import socket

# 传统写法:需手动 close(),存在资源泄漏风险
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('www.example.com', 80))
client.sendall(b'GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n')

# 循环读取,直到服务器关闭连接
resp = bytearray()
while True:
    chunk = client.recv(4096)  # 单次最多读取 4 KB
    if not chunk:
        break
    resp.extend(chunk)
client.close()

# 简单展示响应头
header, _, body = resp.partition(b'\r\n\r\n')
print("响应头预览:\n", header.decode('utf-8')[:500])

Utilize Context Manager(withstatement), Python will automatically close the socket at the end of the code block, and resources can be safely released regardless of whether an exception occurs.

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
    client.connect(('www.example.com', 80))
    client.sendall(b'GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n')
    resp = client.recv(4096)  # 仅读取前 4 KB 作为演示
    print(resp.decode('utf-8'))

3. TCP server programming

The core process of the server is: Create socket → Bind address and port → Start listening → Accept connection → Process request → Close connection.

3.1 Multi-threaded Echo Server (Basic Edition)

This version creates a new thread for each client to implement the "echo" function - return the received data as it is, and when encountering empty data orexitDisconnect on command.

import socket
import threading

def handle_client(conn, addr):
    """处理单个客户端连接"""
    print(f"✅ 新连接来自 {addr}")
    try:
        with conn:  # with 语句确保连接自动关闭
            conn.sendall(b"Welcome to Echo Server!\n")
            while True:
                data = conn.recv(1024)
                # 收到空数据或客户端输入 exit 时退出循环
                if not data or data.strip().lower() == b"exit":
                    break
                conn.sendall(b"Echo: " + data)
    finally:
        print(f"❌ 连接 {addr} 已断开")

def start_server(host="127.0.0.1", port=65432):
    """启动主服务器"""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        # 允许端口立即重用,避免 TIME_WAIT 状态导致重启失败
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind((host, port))
        s.listen()  # 默认积压连接数为 5
        print(f"🚀 服务器启动,监听于 {host}:{port}")

        try:
            while True:
                conn, addr = s.accept()        # 阻塞等待新连接
                t = threading.Thread(
                    target=handle_client,
                    args=(conn, addr)
                )
                t.start()
                print(f"🔍 当前活跃连接数: {threading.active_count() - 1}")
        except KeyboardInterrupt:
            print("\n🛑 收到停止信号,服务器关闭中...")

if __name__ == "__main__":
    start_server()

3.2 Production-level simple optimized version

The basic version above does not limit the number of concurrent connections, nor does it set a timeout mechanism. Malicious connections may exhaust thread resources. Use belowconcurrent.futures.ThreadPoolExecutorTo improve:

import socket
import concurrent.futures

def handle_client(conn, addr):
    print(f"✅ 新连接来自 {addr}")
    # 设置 30 秒超时,防止僵尸连接长期占用线程
    conn.settimeout(30.0)
    try:
        with conn:
            conn.sendall(b"Welcome to Optimized Server!\n")
            while True:
                try:
                    data = conn.recv(1024)
                    if not data or data.strip().lower() == b"exit":
                        break
                    conn.sendall(b"Processed: " + data.upper())
                except socket.timeout:
                    print(f"⏰ 连接 {addr} 超时")
                    break
    except Exception as e:
        print(f"⚠️ 处理 {addr} 时出错: {e}")
    finally:
        print(f"❌ 连接 {addr} 已断开")

def start_server(host="127.0.0.1", port=65432, max_workers=10):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind((host, port))
        s.listen()
        print(f"🚀 优化版服务器启动,监听于 {host}:{port},最大并发 {max_workers}")

        # 使用线程池控制并发数量
        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
            try:
                while True:
                    conn, addr = s.accept()
                    executor.submit(handle_client, conn, addr)
            except KeyboardInterrupt:
                print("\n🛑 收到停止信号,服务器关闭中...")

if __name__ == "__main__":
    start_server()

Through the thread pool, we limit the number of requests processed at the same time to avoid resource exhaustion; the timeout setting prevents some connections from being unresponsive for a long time but occupying the thread.


4. Quick addition: UDP Socket

UDP is a connectionless, unreliable protocol that does not require a pre-established connection and has smaller latency, but may lose data or be out of order. It is very suitable for live video streaming, online games and other scenarios. The code below demonstrates a simple UDP server and client.

import socket

def udp_client():
    """UDP 客户端:发送几条消息"""
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        msgs = [b"Hello", b"UDP", b"World"]
        for msg in msgs:
            s.sendto(msg, ("127.0.0.1", 65433))
            data, addr = s.recvfrom(1024)
            print(f"📨 收到 {addr} 的回复: {data.decode()}")

def udp_server():
    """UDP 服务器:接收消息并转为大写后回复"""
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        s.bind(("127.0.0.1", 65433))
        print("🚀 UDP 服务器启动,监听于 127.0.0.1:65433")
        try:
            while True:
                data, addr = s.recvfrom(1024)
                print(f"📨 收到 {addr}: {data.decode()}")
                s.sendto(data.upper(), addr)
        except KeyboardInterrupt:
            print("\n🛑 服务器关闭")

if __name__ == "__main__":
    # 分别运行 udp_server() 和 udp_client() 进行测试
    udp_server()

5. Best practices and pitfalls to avoid

Quick check to avoid pitfalls

Frequently Asked QuestionsSolutions
Appears when restarting the serverAddress already in useinbind()add befores.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
recv()Received messages are truncated or stuck togetherTCP is a stream protocol and requires the design of an application layer protocol (such as attaching a length to the message header)
Thread or process exhaustionUse thread pool/process pool, or switch to asynchronous I/O (asyncio

General Advice

  1. Resource Management: Always usewithortry/finallyRelease socket resources.
  2. Set Timeout: Passedsettimeout()Avoid long-term blocking of connections.
  3. Production environment: Give priority to mature asynchronous frameworks (such asasyncioTornadoFastAPIunderlying facilities).
  4. usesendall()substitutesend()sendall()Will continue to try to send until all data is written, andsend()It is possible to send only part of it and then return it.

6. Summary

This article demonstrates Python through concise, runnable codesocketCore usage of the module:

  • TCP’s three-way handshake, four-way wave and the nature of streaming communication;
  • From traditional manualclose()Writing style transitions to modernwithcontext manager;
  • From simple multi-threaded Echo server to thread pool + timeout production-level optimization;
  • Example of connectionless communication via UDP;
  • Pitfalls that are easily encountered during development and corresponding solutions.

If the business scenario is more complex, you can learn moreasyncioAsynchronous Socket, SSL/TLS encryption (ssl.create_default_context()) and other advanced themes to build higher-performance and more secure network applications.