摘要
本文基于Python网络编程的学习笔记,系统梳理了网络通信的基础理论(IP、端口、TCP协议、编码解码),并通过详细的代码示例演示了Socket服务端与客户端的实现。同时,文章深入探讨了多进程并发服务端的构建,涵盖进程创建、join等待机制、Lock/RLock进程锁、守护进程以及进程间数据隔离等关键知识点。旨在帮助读者从零开始掌握Python网络编程的核心技能,并理解高并发服务端的基本设计思路。


目录

  1. 网络编程必备理论基础

  2. Socket套接字编程实战(TCP)

  3. 服务端与客户端交互代码详解

  4. 多进程并发服务端设计

  5. 进程同步与状态控制

  6. 进程间数据隔离与通信需求

  7. 总结与避坑指南

一、网络编程必备理论基础

1.1 IP地址与端口号

  • IP地址:网络环境中每台设备的唯一标识,用于定位主机。分为IPv4(4字节,如192.168.1.1)和IPv6(16字节,十六进制表示)。

  • 端口号:标识主机上特定进程的逻辑地址,取值范围 0~65535

    • 系统端口(0-1023):如HTTP(80)、HTTPS(443),需管理员权限绑定。

    • 动态端口(1024-65535):应用程序常用范围,如MySQL默认3306

  • 通信五元组(源IP, 源端口, 目的IP, 目的端口, 协议) 唯一确定一个网络连接。

1.2 TCP协议核心特点

  • 面向连接:通信前必须通过 三次握手 建立可靠连接,结束后 四次挥手 释放资源。

  • 可靠传输:通过序列号、确认应答、重传机制保证数据无差错、不丢失、不重复且按序到达。

  • 适用场景:文件下载、网页浏览、邮件传输等对数据完整性要求高的应用。

1.3 编码与解码——网络传输的基石

网络传输只认字节流(bytes),因此所有字符串必须经过编解码处理:

  • 编码str.encode('utf-8') 将字符串转为字节串。

  • 解码bytes.decode('utf-8') 将字节串还原为字符串。

  • 注意:英文字母/数字在UTF-8中占1字节,中文占3字节;在GBK中中文占2字节。收发双方编码格式必须一致,否则会出现乱码。

二进制字面量快捷写法b'hello' 等价于 'hello'.encode('utf-8')


<a name="2"></a>

二、Socket套接字编程实战(TCP)

Socket(套接字)是进程间网络通信的工具,Python通过内置的socket模块提供底层网络接口。

2.1 创建Socket对象

python

import socket

# AF_INET: IPv4地址族,SOCK_STREAM: TCP协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

参数说明:

  • family:地址族,AF_INET代表IPv4,AF_INET6代表IPv6。

  • type:套接字类型,SOCK_STREAM为TCP,SOCK_DGRAM为UDP。


<a name="3"></a>

三、服务端与客户端交互代码详解

下面通过一个完整的TCP回显程序,展示服务端与客户端的基本交互流程。

3.1 TCP服务端实现步骤

python

import socket

# 1. 创建服务端socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 绑定IP和端口('0.0.0.0'表示监听所有可用网络接口)
server_socket.bind(('0.0.0.0', 8888))

# 3. 设置监听队列(最大等待连接数)
server_socket.listen(10)
print("服务端已启动,等待客户端连接...")

# 4. 阻塞等待客户端连接
client_socket, client_addr = server_socket.accept()
print(f"客户端 {client_addr} 连接成功!")

# 5. 收发数据
client_socket.send(b'Hello from server!')          # 发送字节数据
data = client_socket.recv(1024).decode('utf-8')    # 接收并解码
print(f"收到客户端消息:{data}")

# 6. 关闭连接,释放资源
client_socket.close()
server_socket.close()

3.2 TCP客户端实现步骤

python

import socket

# 1. 创建客户端socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 连接服务端
client_socket.connect(('127.0.0.1', 8888))

# 3. 接收服务端消息
server_data = client_socket.recv(1024).decode('utf-8')
print(f"服务端说:{server_data}")

# 4. 发送响应
client_socket.send('我是客户端,很高兴认识你!'.encode('utf-8'))

# 5. 关闭连接
client_socket.close()

运行流程

  1. 先启动服务端,进入监听状态。

  2. 再启动客户端,发起连接。

  3. 服务端accept()返回新套接字,双方开始通信。

  4. 双方各自关闭连接。


<a name="4"></a>

四、多进程并发服务端设计

上述服务端一次只能处理一个客户端连接,无法应对多用户同时访问的场景。使用 多进程(multiprocessing) 可以让服务端为每个客户端连接创建一个独立进程进行处理,从而实现并发。

4.1 进程基础:主进程与子进程

  • 进程:操作系统资源分配的最小单位,每个进程拥有独立的内存空间。

  • 线程:CPU调度的基本单位,同一进程内的线程共享内存。

  • 并发:单核CPU通过时间片轮转,宏观上多个任务同时推进。

  • 并行:多核CPU真正同时执行多个任务。

使用multiprocessing.Process创建子进程:

python

from multiprocessing import Process
import os

def worker():
    print(f"子进程PID: {os.getpid()}, 父进程PID: {os.getppid()}")

if __name__ == '__main__':
    p = Process(target=worker)
    p.start()   # 启动子进程
    p.join()    # 主进程等待子进程结束
    print("主进程结束")

4.2 多进程版TCP服务端

python

import socket
from multiprocessing import Process

def handle_client(client_sock, addr):
    """处理单个客户端连接的函数"""
    print(f"新连接来自 {addr}")
    while True:
        data = client_sock.recv(1024)
        if not data:   # 客户端断开连接
            break
        print(f"{addr} 说: {data.decode()}")
        client_sock.send(b"Echo: " + data)
    client_sock.close()
    print(f"连接 {addr} 已关闭")

def main():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(('0.0.0.0', 8888))
    server.listen(10)
    print("多进程服务端运行中...")

    while True:
        client_sock, addr = server.accept()
        # 为每个客户端创建一个新进程
        p = Process(target=handle_client, args=(client_sock, addr))
        p.start()   # 子进程启动后,主进程立即回到accept状态,继续接收新连接

if __name__ == '__main__':
    main()

关键点

  • 主进程仅负责监听和接受连接(accept)。

  • 每次新连接到达,创建一个子进程专门处理该客户端的所有通信。

  • 子进程与主进程相互独立,互不阻塞。


<a name="5"></a>

五、进程同步与状态控制

在多进程服务端中,虽然各子进程处理不同客户端,但有时仍需对共享资源(如日志文件)进行同步控制。此外,主进程需要合理管理子进程的生命周期。

5.1 join()方法——主进程等待子进程

join()用于阻塞当前进程,直到调用join()的进程执行完毕。

python

from multiprocessing import Process
import time

def task():
    print("子进程开始")
    time.sleep(2)
    print("子进程结束")

p = Process(target=task)
p.start()
p.join()          # 主进程在此等待,直到p运行结束
print("主进程继续执行")
  • join(timeout):限时等待,超时后主进程不再等待,继续执行。

  • 注意join()必须放在start()之后。

5.2 守护进程(Daemon)

守护进程的特点是:当主进程结束时,守护子进程会被立即强制终止

python

p = Process(target=func, daemon=True)  # 必须在start()前设置
p.start()
# 主进程退出时,p会被自动杀掉,无需手动terminate

应用场景:监控进程、心跳检测进程,这些进程的生命周期应跟随主进程。

5.3 强制终止子进程 terminate()

python

p.terminate()   # 强制杀死子进程(可能导致僵尸进程)
p.join()        # 等待进程真正结束,回收资源

建议:尽量避免使用terminate(),优先让子进程正常退出。若必须使用,务必调用join()回收资源。

5.4 进程锁 Lock 与 RLock

当多个子进程需要操作同一资源(如写入同一个文件)时,必须使用锁来避免竞态条件。

  • Lock:普通互斥锁,不可重入(同一进程内不能连续acquire两次)。

  • RLock:可重入锁,允许同一进程多次获取锁,但必须相应次数释放。

python

from multiprocessing import Process, Lock

def worker(lock, num):
    with lock:   # 自动acquire和release,推荐使用上下文管理器
        print(f"进程{num}正在操作临界资源")

if __name__ == '__main__':
    lock = Lock()
    processes = [Process(target=worker, args=(lock, i)) for i in range(5)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

Lock vs RLock 对比

  • Lock:如果同一进程在未释放的情况下再次acquire,会造成死锁。

  • RLock:记录获取次数,可多次获取,但最终释放次数需匹配。


<a name="6"></a>

六、进程间数据隔离与通信需求

6.1 进程间数据不共享

重要特性:每个进程拥有独立的内存空间,全局变量在子进程中修改不会影响其他进程或主进程。

python

from multiprocessing import Process

num = 0

def adder():
    global num
    num += 10
    print(f"子进程内 num = {num}")

if __name__ == '__main__':
    p = Process(target=adder)
    p.start()
    p.join()
    print(f"主进程内 num = {num}")   # 输出仍为0,子进程修改不影响主进程

6.2 进程间通信方式(IPC)

若需要进程间交换数据,可使用以下方式:

  • Queue:多进程安全的队列。

  • Pipe:管道,用于两个进程间双向通信。

  • Value / Array:共享内存(需配合Lock使用)。

  • Manager:更高级的共享数据结构(如字典、列表)。

示例:使用Queue在服务端主进程和工作子进程之间传递数据。

python

from multiprocessing import Process, Queue

def worker(q):
    q.put("子进程发来的消息")

if __name__ == '__main__':
    q = Queue()
    p = Process(target=worker, args=(q,))
    p.start()
    print(q.get())   # 主进程接收
    p.join()

<a name="7"></a>

七、总结与避坑指南

7.1 核心知识点回顾

技术点 关键作用
Socket 网络通信端点,SOCK_STREAM为TCP
bind/listen/accept 服务端标准启动流程
connect/send/recv 客户端通信流程
多进程 实现并发服务,每个连接一个进程
join() 主进程等待子进程结束
daemon 守护进程随主进程退出而终止
Lock/RLock 保证多进程对共享资源访问的互斥
数据隔离 全局变量在进程间不共享,需IPC通信

7.2 常见错误及解决方案

  • Address already in use:端口被占用。解决方案:服务端bind前设置server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

  • BrokenPipeError:向已关闭的连接发送数据。应在发送前判断连接状态,并捕获异常。

  • 僵尸进程:子进程已结束但父进程未调用join()回收资源。解决方法:父进程务必调用join(),或使用signal处理SIGCHLD

  • 编码不一致导致乱码:客户端与服务端统一使用utf-8编解码。

7.3 拓展学习方向

  • 多线程并发threading模块适用于I/O密集型任务,相比进程更轻量。

  • 异步I/Oasyncio基于协程,单线程高并发,适合大量长连接场景。

  • UDP编程socket.SOCK_DGRAM无连接,适用于实时音视频传输。

  • socketserver框架:Python标准库封装好的TCP/UDP服务器框架,简化多线程/多进程服务端开发。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐