杰瑞科技汇

python简单的多客户端聊天室程序

  1. 服务器端:负责接收客户端连接,并将一个客户端发送的消息广播给所有其他客户端。
  2. 客户端:负责连接到服务器,发送用户输入的消息,并接收来自服务器的其他消息。

服务器端代码 (server.py)

这个脚本会监听来自客户端的连接,并为每个连接创建一个新的线程来处理。

# server.py
import socket
import threading
# 定义服务器的地址和端口
HOST = '127.0.0.1'  # 本地主机地址
PORT = 12345        # 任意非特权端口
# 创建一个 socket 对象
# AF_INET 表示使用 IPv4 地址
# SOCK_STREAM 表示使用 TCP 协议
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
# 开始监听传入的连接
# backlog 参数指定了可以挂起的最大连接数
server.listen()
print(f"服务器正在监听 {HOST}:{PORT}...")
# 用于存储所有已连接的客户端
clients = []
# 用于存储所有客户端的用户名
usernames = []
# 广播消息给所有客户端
def broadcast(message):
    for client in clients:
        client.send(message)
# 处理单个客户端连接的函数
def handle_client(client):
    # 获取客户端的用户名
    username = client.recv(1024).decode('utf-8')
    usernames.append(username)
    clients.append(client)
    # 广播新用户加入的消息
    broadcast(f"{username} 已加入聊天室!\n".encode('utf-8'))
    print(f"{username} 已连接。")
    while True:
        try:
            # 接收来自客户端的消息
            message = client.recv(1024)
            if not message:
                break
            # 广播消息给其他客户端
            broadcast(message)
        except:
            # 如果发生错误(例如客户端断开连接),则移除该客户端
            index = clients.index(client)
            clients.remove(client)
            username = usernames[index]
            usernames.pop(index)
            broadcast(f"{username} 已离开聊天室,\n".encode('utf-8'))
            print(f"{username} 已断开连接。")
            break
    # 关闭客户端连接
    client.close()
# 主循环,用于接受新的客户端连接
while True:
    # 接受新的客户端连接
    client, address = server.accept()
    print(f"已接受来自 {address} 的连接。")
    # 为每个新客户端创建一个新线程
    thread = threading.Thread(target=handle_client, args=(client,))
    thread.start()

客户端代码 (client.py)

这个脚本允许用户连接到服务器,通过控制台输入和接收消息。

# client.py
import socket
import threading
import sys
# 定义服务器的地址和端口 (必须与服务器端一致)
HOST = '127.0.0.1'
PORT = 12345
def receive_messages(client_socket):
    """持续接收来自服务器的消息的线程函数"""
    while True:
        try:
            message = client_socket.recv(1024).decode('utf-8')
            if not message:
                break
            # 打印消息,如果消息是系统通知(如用户加入/离开),可以特殊处理
            if message.endswith(" 已加入聊天室!\n") or message.endswith(" 已离开聊天室,\n"):
                print(f"\n{message.strip()}", end="> ")
            else:
                # 避免打印自己的输入
                if not message.startswith("你: "):
                    print(f"\n{message.strip()}", end="> ")
        except:
            print("连接已丢失。")
            break
    client_socket.close()
    sys.exit() # 退出程序
def send_messages(client_socket):
    """处理用户输入并发送消息的函数"""
    # 首先要求用户输入用户名
    username = input("请输入您的用户名: ")
    client_socket.send(username.encode('utf-8'))
    while True:
        message = input("> ")
        if message.lower() == 'exit':
            client_socket.send("用户已退出。".encode('utf-8'))
            break
        full_message = f"你: {message}"
        client_socket.send(full_message.encode('utf-8'))
# 创建一个 socket 对象
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    # 连接到服务器
    client.connect((HOST, PORT))
    print(f"已连接到服务器 {HOST}:{PORT}")
    # 创建并启动接收消息的线程
    receive_thread = threading.Thread(target=receive_messages, args=(client,))
    receive_thread.daemon = True # 设置为守护线程,这样主线程退出时它也会退出
    receive_thread.start()
    # 在主线程中发送消息
    send_messages(client)
except ConnectionRefusedError:
    print("无法连接到服务器,请确保服务器正在运行。")
except Exception as e:
    print(f"发生错误: {e}")
finally:
    client.close()
    print("客户端已关闭。")

如何运行

  1. 保存文件:将上面的两段代码分别保存为 server.pyclient.py,放在同一个文件夹下。

  2. 启动服务器

    • 打开一个终端(或命令提示符)。
    • 使用 cd 命令切换到你保存文件的目录。
    • 运行服务器脚本:
      python server.py
    • 你会看到输出:服务器正在监听 127.0.0.1:12345...,表示服务器已启动并等待连接。
  3. 启动客户端

    • 打开另一个新的终端(非常重要,不要在同一个终端里运行)。
    • 同样,切换到文件所在的目录。
    • 运行客户端脚本:
      python client.py
    • 客户端会提示你输入用户名,输入后按回车。
  4. 启动更多客户端

    • 重复步骤 3,打开第三个、第四个...终端,运行 python client.py
    • 每当新客户端加入时,所有已连接的客户端都会收到“XXX 已加入聊天室!”的通知。
    • 当一个客户端输入消息并按回车时,其他所有客户端都会看到该消息。
    • 当一个客户端输入 exit 并按回车时,它会断开连接,其他客户端会收到“XXX 已离开聊天室。”的通知。

代码解析

服务器端 (server.py)

  • socket.socket()bind(): 创建网络套接字并将其绑定到指定的 IP 地址和端口。
  • listen(): 使服务器进入监听模式,准备接受客户端连接。
  • clientsusernames 列表: 全局列表,用于跟踪所有连接的客户端套接字和它们对应的用户名。
  • broadcast(message): 一个辅助函数,遍历 clients 列表,将消息发送给每一个客户端。
  • handle_client(client): 这是核心的处理函数,它在一个独立的线程中运行,专门为一个客户端服务。
    • 首先接收并存储客户端的用户名。
    • 使用 while True 循环持续接收该客户端的消息。
    • client.recv(1024): 从套接字读取最多 1024 字节的数据。
    • recv() 返回空数据,表示客户端已正常断开连接。
    • 如果发生异常(如网络错误),表示客户端异常断开,此时会从列表中移除该客户端并广播其离开的消息。
  • while True 循环: 持续调用 server.accept() 来等待新的客户端连接,每当有新客户端连接,就为它创建一个新的 handle_client 线程,这样服务器就可以同时处理多个客户端而不会阻塞。

客户端 (client.py)

  • receive_messages(client_socket): 在一个独立的线程中运行。
    • 它的唯一任务就是不停地接收来自服务器的消息并打印到控制台。
    • 这样做可以防止在等待服务器消息时,用户无法输入新消息。
  • send_messages(client_socket): 在主线程中运行。
    • 它首先让用户输入用户名,并发送给服务器。
    • 它进入一个循环,使用 input() 获取用户输入,并将消息(加上“你: ”前缀)发送给服务器。
    • 如果用户输入 exit,则关闭连接并退出。
  • threading.Thread: 客户端使用两个线程,一个用于接收,一个用于发送,这使得客户端可以同时进行I/O操作,实现了全双工通信,接收线程被设置为守护线程 (daemon=True),这样当主线程(发送线程)结束时,程序会自动退出,确保资源被释放。

这个例子非常经典,是学习网络编程和多线程的绝佳入门项目,你可以基于此进行扩展,例如添加私聊功能、文件传输、图形用户界面等。

分享:
扫描分享到社交APP
上一篇
下一篇