人人都会AI编程

17.1 Socket 编程基础:TCP/UDP 通信、服务端与客户端实现

更新时间:2026-07-12

Socket(套接字)是网络编程的底层接口,可以把它理解为网络上的“插座”:两台机器上的程序通过各自的 socket 建立连接,就能像读写文件一样收发数据。Python 的 socket 模块提供了标准的 BSD Socket API,封装良好,可以轻松实现跨平台的网络通信。

核心概念:TCP vs UDP

两者都是传输层协议,但设计目标不同,选型直接决定了通信的行为。

  • TCP(传输控制协议)
  • 面向连接:通信前需要通过“三次握手”建立连接,结束后“四次挥手”断开。
  • 可靠传输:数据不会丢失、不会乱序、不会重复,底层有超时重传、流量控制等机制。
  • 流式传输:发送方多次发送的数据在接收方可能被合并或拆分,没有天然的消息边界。实际编程中需要自己定义消息边界(如长度前缀、特殊分隔符)。
  • 适用场景:HTTP、WebSocket、文件传输、邮件、远程登录等——任何不能容忍数据丢失的场景。
  • UDP(用户数据报协议)
  • 无连接:直接发送数据报,不需要建立连接,开销低。
  • 不保证可靠:数据可能丢包、乱序、重复,应用程序需要自行处理。
  • 保留消息边界:每个数据报都是一个完整单位,不会粘包。
  • 适用场景:视频直播、语音通话、实时游戏、DNS 查询等——能容忍少量丢包但要求低延迟的场景。

TCP 通信实现

服务端(server)

import socket

def tcp_server(host='127.0.0.1', port=9999):
    # 1. 创建 socket 对象 (AF_INET 表示 IPv4, SOCK_STREAM 表示 TCP)
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
        # 2. 绑定地址和端口
        server_socket.bind((host, port))
        # 3. 开始监听,参数是等待连接的最大排队数
        server_socket.listen(5)
        print(f"TCP 服务端启动,监听 {host}:{port}")
        
        while True:
            # 4. 接受客户端连接,返回新 socket 和客户端地址
            client_socket, addr = server_socket.accept()
            with client_socket:
                print(f"客户端 {addr} 已连接")
                # 5. 接收数据(最多接收 1024 字节)
                data = client_socket.recv(1024)
                if data:
                    print(f"收到: {data.decode('utf-8')}")
                    # 6. 发送响应
                    client_socket.sendall(f"服务端已收到: {data.decode('utf-8')}".encode('utf-8'))
        # 循环结束后自动关闭 server_socket

客户端(client)

import socket

def tcp_client(host='127.0.0.1', port=9999, message='Hello TCP'):
    # 1. 创建 socket,同样使用 AF_INET + SOCK_STREAM
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
        # 2. 连接服务端
        client_socket.connect((host, port))
        # 3. 发送数据
        client_socket.sendall(message.encode('utf-8'))
        # 4. 接收响应
        response = client_socket.recv(1024)
        print(f"服务端响应: {response.decode('utf-8')}")

运行要点

  • 先启动服务端,后启动客户端。服务端 accept() 会阻塞等待客户端连接。
  • 上述示例是一次性交互,实际长连接需要循环收发。
  • sendall() 保证全部数据发出,而 send() 可能只发送一部分,需要自己处理余下内容。
  • TCP 粘包问题:如果连续发送多次小数据,接收方可能一次 recv() 读到多个消息。简单的处理方式是用特殊字符(如 \n)分隔消息,或在消息前添加固定长度的长度字段。

UDP 通信实现

UDP 通信双方都可以直接用 sendtorecvfrom,没有连接的概念。

简单回显服务端

import socket

def udp_server(host='127.0.0.1', port=9999):
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server_socket:
        server_socket.bind((host, port))
        print(f"UDP 服务端启动,监听 {host}:{port}")
        
        while True:
            # 接收数据和客户端地址
            data, addr = server_socket.recvfrom(1024)
            print(f"收到来自 {addr} 的消息: {data.decode('utf-8')}")
            # 回复客户端
            server_socket.sendto(f"已收到: {data.decode('utf-8')}".encode('utf-8'), addr)

客户端

import socket

def udp_client(host='127.0.0.1', port=9999, message='Hello UDP'):
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_socket:
        # 直接发送数据报,不需要 connect
        client_socket.sendto(message.encode('utf-8'), (host, port))
        # 接收回复
        data, server_addr = client_socket.recvfrom(1024)
        print(f"服务端回复: {data.decode('utf-8')}")

UDP 也可以使用 connect()
虽然 UDP 无连接,但调用 connect((host, port)) 后可以绑定远程地址,后续直接用 send()recv()(不需要 sendto),方便固定通信对象的程序。

关键注意事项

  • 异常处理:网络通信不稳定,实际代码中务必用 try/except 捕获 socket.errorConnectionResetError 等异常,避免程序崩溃。
  • 资源释放:尽量使用 with 语句或 finally 确保 socket 关闭;不关闭会造成端口占用或资源泄漏。
  • 字节编码:网络传输只能是字节流,发送字符串前需 .encode('utf-8'),接收后需 .decode('utf-8')。双方编码必须一致。
  • 阻塞与非阻塞:默认 socket 是阻塞模式,accept()recv() 会卡住整个线程。高并发场景下可结合 selectasyncio 或使用多线程/多进程处理。
  • 端口复用:服务端重启时可能遇到端口仍被占用(TIME_WAIT 状态),可在 bind() 前设置 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 快速重用端口。

掌握这些基础后,就可以动手实现简单的网络工具、文件传输脚本或自定义协议的服务,也为理解 Web 框架底层原理和网络编程模型打下扎实基础。