当前位置:首页>python>【一起学 Python】第 70 天:Socket 编程基础启航

【一起学 Python】第 70 天:Socket 编程基础启航

  • 2026-10-11 08:35:52
【一起学 Python】第 70 天:Socket 编程基础启航

欢迎来到 Python 学习计划的第 70 天!🎉

恭喜你完成了 进阶阶段:类型提示与工程化(第 51-69 天) 的全部学习!从今天开始,我们将进入 网络编程模块:Socket 编程基础(第 70-73 天)。

这是 Python 网络编程的核心基础!掌握 Socket 编程,你将能够:

  • 理解网络通信的底层原理
  • 构建 TCP/UDP 网络应用
  • 实现客户端 - 服务器架构
  • 为学习 Web 开发、分布式系统打下坚实基础

📚 什么是 Socket?

Socket(套接字)是操作系统提供的网络编程接口,它允许应用程序通过网络进行通信。Python 的 socket 模块封装了底层的 BSD Socket API,使我们可以方便地进行网络编程。

import socket# 创建一个 TCP Sockettcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 创建一个 UDP Socketudp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

Socket 就像一个"电话",通过它你可以:

  • 建立连接(像拨号一样)
  • 发送数据(像说话一样)
  • 接收数据(像听音一样)
  • 关闭连接(像挂电话一样)

🔑 核心概念

Socket 基础组件

1. 地址族(Address Family)

指定 Socket 使用的网络协议版本

地址族

常量

说明

IPv4

AF_INET

使用 IPv4 地址(最常用)

IPv6

AF_INET6

使用 IPv6 地址

Unix

AF_UNIX

本地进程间通信

2. Socket 类型

指定通信方式:

类型

常量

说明

TCP

SOCK_STREAM

流式套接字,面向连接,可靠传输

UDP

SOCK_DGRAM

数据报套接字,无连接,不保证可靠

Raw

SOCK_RAW

原始套接字,直接访问网络层

3. 网络地址表示

网络通信需要知道目标的两个信息:

# 主机地址 + 端口号 = 完整网络地址address = ('127.0.0.1', 8888)#          └─ 主机      └─ 端口

常见主机地址:

  • 127.0.0.1
     - 本机回环地址(localhost)
  • 0.0.0.0
     - 任意地址(通常用于服务器绑定)
  • 192.168.1.1
     - 局域网地址

端口号范围:

  • 0-1023
     - 系统保留端口(需要管理员权限)
  • 1024-65535
     - 普通用户可使用(通常选择 1024 以上)

🖥️ TCP 编程:面向连接的通信

TCP(Transmission Control Protocol)是面向连接的协议,通信前需要建立连接,通信完成后需要关闭连接。

TCP 三次握手

在建立 TCP 连接时,客户端和服务器需要通过三次握手来同步状态:

握手过程:

  1. 第一次握手:客户端发送 SYN包,告诉服务器"我要连接你"
  2. 第二次握手:服务器回复 SYN-ACK包,表示"我收到了,我也同意接"
  3. 第三次握手:客户端发送 ACK包,表示"我收到了你的同意"

三次握手完成后,连接建立,双方可以开始传输数据。

TCP 通信流程

TCP 服务器实现

TCP 服务器的基本步骤:socket() → bind() → listen() → accept() → recv()/send() → close()

完整示例:

import socketdef tcp_server(host='127.0.0.1', port=8888):    # 1. 创建 Socket    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    # 2. 设置地址重用,避免"Address already in use"错误    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)    # 3. 绑定地址和端口    server.bind((host, port))    print(f"服务器启动,监听 {host}:{port}")    # 4. 开始监听(参数是等待队列的最大长度)    server.listen(5)    try:        while True:            # 5. 接受客户端连接            client_socket, client_addr = server.accept()            print(f"✅ 客户端连接: {client_addr}")            try:                while True:                    # 6. 接收数据                    data = client_socket.recv(1024)                    if not data:                        print(f"客户端 {client_addr} 关闭连接")                        break                    message = data.decode('utf-8')                    print(f"📨 收到: {message}")                    # 7. 发送响应                    response = f"服务器已收到: {message}"                    client_socket.send(response.encode('utf-8'))            finally:                # 8. 关闭客户端连接                client_socket.close()    except KeyboardInterrupt:        print("\n服务器关闭")    finally:        server.close()if __name__ == "__main__":    tcp_server()

关键点解释:

  • socket()
     - 创建 Socket 对象
  • bind()
     - 将 Socket 绑定到特定的地址和端口
  • listen()
     - 将 Socket 设置为监听状态,参数 5 表示等待队列最多容纳 5 个连接
  • accept()
     - 阻塞等待客户端连接,返回连接后的 Socket 和客户端地址
  • recv(n)
     - 最多接收 n 字节的数据
  • send()
     - 发送数据,返回实际发送的字节数

TCP 客户端实现

TCP 客户端的基本步骤:

socket() → connect() → send()/recv() → close()

完整示例:

import socketdef tcp_client(host='127.0.0.1', port=8888):    # 1. 创建 Socket    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    try:        # 2. 连接到服务器        client.connect((host, port))        print(f"✅ 已连接到服务器 {host}:{port}")        # 3. 发送数据        message = "Hello, Server!"        client.send(message.encode('utf-8'))        print(f"📤 发送: {message}")        # 4. 接收响应        response = client.recv(1024)        print(f"📥 收到响应: {response.decode('utf-8')}")    except ConnectionRefusedError:        print("❌ 连接被拒绝,请检查服务器是否运行")    except Exception as e:        print(f"❌ 错误: {e}")    finally:        # 5. 关闭 Socket        client.close()if __name__ == "__main__":    tcp_client()

使用上下文管理器(推荐写法)

Python 的 with 语句可以自动管理 Socket 资源:

import socket# 服务器端def tcp_server_context():    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)        server.bind(('127.0.0.1', 8888))        server.listen(5)        conn, addr = server.accept()        with conn:            print(f"连接来自: {addr}")            data = conn.recv(1024)            conn.sendall(data)  # sendall 确保发送完整数据# 客户端def tcp_client_context():    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:        client.connect(('127.0.0.1', 8888))        client.sendall(b"Hello")        data = client.recv(1024)        print(f"收到: {data}")

🌐 UDP 编程:无连接的通信

UDP(User Datagram Protocol)是无连接协议,不需要建立连接即可直接发送数据。特点是快速但不保证可靠。

UDP 通信模式

UDP 的特点:

  • 无需建立连接
     - 直接发送数据
  • 快速高效
     - 开销小
  • 不保证可靠
     - 数据可能丢失、重复或乱序
  • 有消息边界
     - 每个 UDP 包是独立的

UDP 服务器实现

import socketdef udp_server(host='127.0.0.1', port=8888):    # 1. 创建 UDP Socket    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)    # 2. 绑定地址    server.bind((host, port))    print(f"UDP 服务器启动,监听 {host}:{port}")    try:        while True:            # 3. 接收数据和发送方地址(无需 accept)            data, client_addr = server.recvfrom(1024)            message = data.decode('utf-8')            print(f"📨 收到来自 {client_addr} 的数据: {message}")            # 4. 发送响应回给客户端            response = f"收到: {message}"            server.sendto(response.encode('utf-8'), client_addr)    except KeyboardInterrupt:        print("\nUDP 服务器关闭")    finally:        server.close()if __name__ == "__main__":    udp_server()

关键点:

  • recvfrom()
     - 接收数据并返回发送方地址
  • sendto()
     - 发送数据到指定地址
  • 无需 listen() 和 accept()

UDP 客户端实现

import socketdef udp_client(host='127.0.0.1', port=8888):    # 1. 创建 UDP Socket    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)    try:        # 2. 发送数据(无需先连接)        message = "Hello, UDP Server!"        client.sendto(message.encode('utf-8'), (host, port))        print(f"📤 发送: {message}")        # 3. 接收响应        data, addr = client.recvfrom(1024)        print(f"📥 收到响应: {data.decode('utf-8')}")    except Exception as e:        print(f"❌ 错误: {e}")    finally:        client.close()if __name__ == "__main__":    udp_client()

📊 TCP vs UDP 对比

特性

TCP

UDP

连接方式

面向连接(需要三次握手)

无连接(直接发送)

可靠性

保证数据到达且有序

不保证可靠,可能丢包

传输速度

较慢(需要握手和确认)

较快(无握手开销)

数据流

字节流,无消息边界

数据报,有消息边界

资源消耗

较高(连接状态维护)

较低(无状态)

应用场景

需要可靠传输

需要实时性

适用场景

TCP 适用于:

  • HTTP/HTTPS(网页浏览)
  • FTP(文件传输)
  • Email(邮件服务)
  • SSH/Telnet(远程登录)
  • 金融交易(需要可靠)

UDP 适用于:

  • 视频/音频直播
  • 在线游戏
  • 语音通话
  • DNS 查询
  • IoT 传感器数据
  • 实时交互应用

🛠️ Socket 常用方法

创建和基础操作

import socket# 创建 Socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定地址sock.bind(('127.0.0.1', 8888))# 开始监听sock.listen(5)# 接受连接conn, addr = sock.accept()# 连接到服务器sock.connect(('127.0.0.1', 8888))# 关闭 Socketsock.close()

数据传输

# TCP 发送sock.send(b"data")        # 返回实际发送的字节数sock.sendall(b"data")     # 确保发送所有数据# TCP 接收data = sock.recv(1024)    # 接收最多 1024 字节# UDP 发送sock.sendto(b"data", ('127.0.0.1', 8888))# UDP 接收data, addr = sock.recvfrom(1024)

Socket 选项

import socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 地址重用(避免 TIME_WAIT 导致的端口占用)sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 禁用 Nagle 算法(减少延迟)sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)# 设置发送缓冲区大小sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536)# 设置接收缓冲区大小sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)# 获取选项值value = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)

📖 完整示例:Echo 服务

一个完整的 Echo 服务示例,演示 TCP 通信的完整流程。

服务器(echo_server.py)

import socketdef echo_server(host='127.0.0.1', port=8888):    """    Echo 服务器:接收客户端消息并原样返回    """    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:        # 设置选项        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)        # 绑定和监听        server.bind((host, port))        server.listen(5)        print(f"🚀 Echo 服务器运行在 {host}:{port}")        print("等待客户端连接...\n")        try:            while True:                # 接受连接                conn, addr = server.accept()                print(f"✅ 客户端连接: {addr[0]}:{addr[1]}")                with conn:                    while True:                        # 接收数据                        data = conn.recv(1024)                        if not data:                            print(f"❌ 客户端 {addr[0]}:{addr[1]} 断开连接\n")                            break                        # 解码并显示                        message = data.decode('utf-8')                        print(f"  📨 收到: {message}")                        # 原样返回                        conn.sendall(data)                        print(f"  📤 回显: {message}")        except KeyboardInterrupt:            print("\n👋 服务器关闭")if __name__ == "__main__":    echo_server()

客户端(echo_client.py)

import socketimport timedef echo_client(host='127.0.0.1', port=8888):    """    Echo 客户端:发送消息并接收回显    """    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:        try:            # 连接到服务器            client.connect((host, port))            print(f"✅ 已连接到服务器 {host}:{port}\n")            # 发送多条消息            messages = ["Hello", "Socket", "Programming", "Python"]            for msg in messages:                # 发送                client.sendall(msg.encode('utf-8'))                print(f"📤 发送: {msg}")                # 接收回显                response = client.recv(1024)                print(f"📥 收到: {response.decode('utf-8')}\n")                time.sleep(0.5)            print("✅ 所有消息已发送")        except ConnectionRefusedError:            print("❌ 连接被拒绝,请先启动服务器")        except Exception as e:            print(f"❌ 错误: {e}")if __name__ == "__main__":    echo_client()

运行方式:

# 终端1:启动服务器python echo_server.py# 终端2:启动客户端python echo_client.py

⚠️ 常见问题解答

Q1:为什么出现 "Address already in use" 错误?

答: 这是因为之前的连接处于 TIME_WAIT 状态,端口还没有释放。解决方法:

# 设置地址重用sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)sock.bind(('127.0.0.1', 8888))

或者等待几分钟后重试。

Q2:send() 和 sendall() 有什么区别?

答:

# send() - 可能只发送部分数据,返回实际发送的字节数sent = sock.send(b"Hello")print(sent)  # 可能 < 5# sendall() - 确保发送所有数据,无返回值sock.sendall(b"Hello")  # 一定发送完整 5 字节# sendall() 的原理:def sendall_logic(sock, data):    total_sent = 0    while total_sent < len(data):        sent = sock.send(data[total_sent:])        if sent == 0:            raise RuntimeError("连接已断开")        total_sent += sent

Q3:如何判断对方已断开连接?

答:

data = sock.recv(1024)if not data:    print("对方已断开连接")    sock.close()

Q4:TCP 的消息边界问题如何处理?

答: TCP 是字节流协议,无消息边界。需要自己定义协议来划分消息。一个常见方法是使用消息长度前缀:

import structdef send_message(sock, msg):    """发送消息(带长度前缀)"""    msg_bytes = msg.encode('utf-8')    # 先发送 4 字节长度    sock.sendall(struct.pack('>I', len(msg_bytes)))    # 再发送实际数据    sock.sendall(msg_bytes)def recv_message(sock):    """接收消息(带长度前缀)"""    # 先接收 4 字节长度    raw_len = sock.recv(4)    if not raw_len:        return None    msg_len = struct.unpack('>I', raw_len)[0]    # 再接收指定长度的数据    return sock.recv(msg_len).decode('utf-8')

💡 最佳实践

使用 with 语句管理资源

with socket.socket() as sock:    # Socket 会自动关闭    pass

设置地址重用

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

正确处理编码

# 发送时编码sock.send(message.encode('utf-8'))# 接收时解码message = data.decode('utf-8')

检查连接状态

data = sock.recv(1024)if not data:    print("连接已断开")

合理设置缓冲区大小

    • 小数据用 1024 字节
    • 大数据用 4096-65536 字节

🎯 小结

  1. Socket 是网络编程的基础接口,分为 TCP 和 UDP 两类
  2. TCP 是面向连接的,需要三次握手建立连接,保证可靠传输
  3. UDP 是无连接的,直接发送数据包,快速但不保证可靠
  4. TCP 适合:需要可靠传输的应用(HTTP、FTP、Email)
  5. UDP 适合:需要实时性的应用(视频直播、游戏、DNS)
  6. 使用 with 语句:可以自动管理 Socket 资源
  7. TCP 消息边界:需要自己处理,可以使用长度前缀

🎉 模块学习路线

┌─────────────────────────────────────────────────────────┐│          Socket 编程基础模块(第 70-73 天)               │├─────────────────────────────────────────────────────────┤│                                                           ││  第 70 天 ✓  Socket API 基础:创建 TCP 与 UDP 连接        ││       │    • Socket 概念与原理                           ││       │    • TCP vs UDP 对比                             ││       │    • 服务器与客户端实现                          ││       │                                                  ││       ▼                                                  ││  第 71 天   Python 3.14 中的异步 Socket 编程              ││       │    • asyncio 与 Socket 结合                      ││       │    • 高并发服务器实现                            ││       │                                                  ││       ▼                                                  ││  第 72 天   Socket 错误处理与超时机制                    ││       │    • 网络异常处理                                ││       │    • 超时设置与重连                              ││       │                                                  ││       ▼                                                  ││  第 73 天   基于 Socket 的简单聊天应用实现               ││            • 综合实战项目                                ││            • 多客户端聊天室                              ││                                                           │└─────────────────────────────────────────────────────────┘

📌 明日预告:Python 3.14 中的异步 Socket 编程

明天我们将进入 Socket 编程模块第二天!

  • 主题:Python 3.14 中的异步 Socket 编程(asyncio 与 Socket)
  • 核心问题:
    1. 如何将 Socket 与 asyncio 结合?
    2. 如何实现高并发 Socket 服务器?
    3. asyncio.start_server() 怎么用?
    4. 异步 Socket 与同步 Socket 有什么区别?
    5. 性能对比如何?

💡 提前思考:

  1. 同步 Socket 处理 1000 个客户端需要什么?
  2. 异步 Socket 为什么能处理更多并发?
  3. await reader.read() 和 recv() 有什么区别?

最新文章

随机文章