Python 网络编程:socket + threading 实现简易聊天系统(含完整源码与实战讲解)

在网络编程的世界里,Socket 是沟通的“电话线”,Threading 是让你可以同时和很多人通话的“线程分机”。将它们组合起来,你就能从零构建一个多人并发的简易聊天服务。

一、Socket + Threading 到底在做什么?

用一句话概括:

一个 socket 就是一条“通信线路”,一个线程就负责“盯着”某个客户端。

所以,一个多人聊天程序就是:

  • 一个主线程:负责接受新连接
  • 多个子线程:各自负责收发对应客户端数据
  • 一个共享消息广播函数

二、Python Socket 基础知识(附实例)

1. 创建一个最基础的 TCP 服务器

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 9999))
server.listen(5)

print("服务器已启动,等待连接...")

conn, addr = server.accept()
print("新客户端:", addr)

data = conn.recv(1024)
print("收到:", data.decode())

conn.send("你好,我是服务器".encode())

优点(正面示例)

  • 简单
  • 清晰

缺点(错误示例)

  • 只能同时服务一个客户端
  • conn.recv() 是阻塞的,客户端不发消息就会卡住

这迫使我们引入 threading。


三、Threading:让服务器能服务多个客户端

多线程模型基本结构

import threading

def handle_client(conn, addr):
    while True:
        data = conn.recv(1024)
        if not data:
            break
        print(f"{addr}{data.decode()}")

    conn.close()

每来一个客户端:

threading.Thread(target=handle_client, args=(conn, addr)).start()

这样服务器就能同时处理多个连接。


四、完整项目:实现一个多人聊天室(含源码)

下面是完整可运行的聊天系统:

服务器 server.py

import socket
import threading

clients = []  # 保存所有客户端 socket

def broadcast(msg, source):
    for c in clients:
        if c != source:
            try:
                c.send(msg)
            except:
                pass

def handle_client(conn, addr):
    print("客户端加入:", addr)
    while True:
        try:
            data = conn.recv(1024)
            if not data:
                break
            print(f"{addr} 说:{data.decode()}")
            broadcast(data, conn)
        except:
            break

    print("客户端退出:", addr)
    clients.remove(conn)
    conn.close()

def main():
    server = socket.socket()
    server.bind(("0.0.0.0", 9999))
    server.listen(5)
    print("聊天室服务器已启动,端口 9999")

    while True:
        conn, addr = server.accept()
        clients.append(conn)
        threading.Thread(target=handle_client, args=(conn, addr)).start()

if __name__ == "__main__":
    main()

功能说明

  • 每个客户端由一个线程负责
  • broadcast() 用于把消息发送给其他用户
  • 客户端退出时自动移除

客户端 client.py

import socket
import threading

def listen(sock):
    while True:
        try:
            msg = sock.recv(1024)
            print(msg.decode())
        except:
            break

def main():
    sock = socket.socket()
    sock.connect(("127.0.0.1", 9999))

    threading.Thread(target=listen, args=(sock,), daemon=True).start()

    while True:
        msg = input("")
        sock.send(msg.encode())

if __name__ == "__main__":
    main()

五、常见错误示例与调试技巧

错误 1:主线程阻塞导致无人能连

错误代码:

conn, addr = server.accept()
data = conn.recv(1024)

当第一个客户端连进来,服务器会一直等待它发数据,后续客户端连不上。

解决:

  • 使用线程处理每个客户端
  • 主线程只负责 accept

错误 2:客户端关闭导致服务器崩溃

某些同学写的 recv 逻辑:

data = conn.recv(1024).decode()
print(data)

当客户端断开,recv 返回空数据或抛异常,程序会崩。

正确写法:

data = conn.recv(1024)
if not data:
    break

错误 3:broadcast 里修改列表导致错误

错误:

for c in clients:
    clients.remove(c)

会触发:

RuntimeError: list changed size during iteration

解决方法:

  • 不在遍历时修改 clients
  • 用 try/except 忽略发送失败

调试技巧

  • print 查看线程是否启动
  • netstat -ano 查看端口是否被占用
  • telnet 127.0.0.1 9999 测试 server 是否正常

六、实际工作技巧:让你的聊天系统更“像样”

技巧 1:给每个用户增加昵称

可以在客户端连接时先发送名字:

name = sock.recv(1024).decode()

技巧 2:支持系统消息

如用户加入、用户退出通知。

技巧 3:心跳检测防止假死

每隔 30 秒发送一次心跳包。

技巧 4:使用队列代替直接 send

生产环境中可避免阻塞。

例如用 queue.Queue:

msg_queue.put(msg)

线程从队列取数据发送。


七、拓展:为什么不用多进程?为什么聊天系统常用 select/epoll?

多线程虽然简单,但在高并发情况下会产生:

  • 线程过多
  • 切换开销大
  • Python GIL 限制性能

所以高并发聊天系统(如 WebSocket)更常用:

  • select(基础)
  • poll(增强)
  • epoll(高并发专用)

本篇教程属于“教学友好、结构清晰”的版本。

如果你想进一步升级,我可以写:

《select + epoll 实现高并发聊天室》

《Python + asyncio 实现万人聊天室》


八、总结

通过本文你掌握的是一个非常典型的网络编程结构:

  1. 用 socket 建立 TCP 通信
  2. 用 threading 支持多个客户端并发
  3. 用广播机制构建聊天模型
  4. 避免常见错误(阻塞、断连、列表操作等)
  5. 学到实际工作中的优化技巧
  6. 理解背后的原理(I/O 模型 + 并发模型)

你完全可以在这个基础上继续拓展,做出自己的多人聊天工具、在线协作系统甚至小游戏服务器。


AI 创作声明

本文部分内容由 AI 辅助生成,并经人工整理与验证,仅供参考学习,欢迎指出错误与不足之处。


Logo

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

更多推荐