杰瑞科技汇

Python socket服务端如何实现多客户端连接?

TCP 是一种面向连接的、可靠的协议,非常适合用于构建需要稳定数据传输的服务(如网页服务器、数据库连接等)。

Python socket服务端如何实现多客户端连接?-图1
(图片来源网络,侵删)

我们将分步进行,从最基础的服务端开始,然后逐步改进,使其能够处理多个客户端的并发连接。


最简单的 TCP 服务端(单连接)

这个例子创建一个服务端,它会等待一个客户端连接,接收客户端发送的数据,打印出来,然后发送一个回复,最后关闭连接。

代码示例

# server_simple.py
import socket
# 1. 创建一个 socket 对象
# socket.AF_INET 表示使用 IPv4 地址
# socket.SOCK_STREAM 表示使用 TCP 协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定 IP 地址和端口号
# '0.0.0.0' 表示监听本机所有可用的网络接口
# 8888 是我们选择的端口号(注意:1024以下的端口需要管理员权限)
host = '0.0.0.0'
port = 8888
server_socket.bind((host, port))
# 3. 开始监听连接
# 5 是连接队列的长度,表示最多有多少个等待连接的客户端
server_socket.listen(5)
print(f"服务器已启动,正在监听 {host}:{port}...")
# 4. 接受客户端连接
# accept() 会阻塞程序,直到有客户端连接上来
# 它返回一个 (client_socket, client_address) 元组
# client_socket 是一个用于与这个特定客户端通信的新的 socket 对象
# client_address 是客户端的 IP 地址和端口号
client_socket, client_address = server_socket.accept()
print(f"已接受来自 {client_address} 的连接!")
# 5. 与客户端进行通信
# 接收数据
# recv(1024) 表示每次最多接收 1024 字节的数据
# recv() 也会阻塞,直到收到数据
data = client_socket.recv(1024)
print(f"收到来自客户端的数据: {data.decode('utf-8')}")
# 发送数据
response = "你好,客户端!"
client_socket.sendall(response.encode('utf-8'))
print("已向客户端发送回复。")
# 6. 关闭连接
# 先关闭与客户端的通信 socket
client_socket.close()
# 再关闭服务端的主 socket
server_socket.close()
print("服务器已关闭。")

如何运行

  1. 保存代码:将上面的代码保存为 server_simple.py

  2. 运行服务端:在终端中执行 python server_simple.py

    Python socket服务端如何实现多客户端连接?-图2
    (图片来源网络,侵删)
    $ python server_simple.py
    服务器已启动,正在监听 0.0.0.0:8888...

    服务端会阻塞在 server_socket.accept() 这一行,等待客户端连接。

  3. 使用客户端连接:打开另一个终端,可以使用 telnetnc (netcat) 作为客户端来测试。

    • 使用 telnet:
      $ telnet 127.0.0.1 8888
      Trying 127.0.0.1...
      Connected to 127.0.0.1.
      Escape character is '^]'.

      然后在 telnet 窗口中输入一些文字,hello,然后按回车。 服务端终端会显示:

      已接受来自 ('127.0.0.1', 54321) 的连接!
      收到来自客户端的数据: hello
      已向客户端发送回复。

      telnet 客户端会收到回复:

      Python socket服务端如何实现多客户端连接?-图3
      (图片来源网络,侵删)
      hello
      你好,客户端!
    • 使用 nc (netcat):
      $ nc 127.0.0.1 8888
      hello
      你好,客户端!
  4. 关闭服务:在服务端终端按 Ctrl+C,程序会执行最后的 close() 操作并退出。


改进版:服务端持续运行并处理多个客户端

上面的例子有一个很大的问题:当一个客户端连接后,服务端处理完就退出了,无法为其他客户端服务。

我们可以使用一个 while True 循环来让服务端持续运行,不断接受新的连接。

代码示例

# server_loop.py
import socket
# 创建 socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 8888))
server_socket.listen(5)
print(f"服务器已启动,正在监听 0.0.0.0:8888...")
# 循环接受连接
while True:
    # 接受新的客户端连接
    client_socket, client_address = server_socket.accept()
    print(f"已接受来自 {client_address} 的连接!")
    try:
        # 接收数据
        data = client_socket.recv(1024)
        if not data:
            # recv() 返回空数据,表示客户端已关闭连接
            print(f"{client_address} 已关闭连接。")
            client_socket.close()
            continue
        print(f"收到来自 {client_address} 的数据: {data.decode('utf-8')}")
        # 发送回复
        response = f"你好,{client_address[0]}!你的消息已收到。"
        client_socket.sendall(response.encode('utf-8'))
    except ConnectionResetError:
        print(f"客户端 {client_address} 强制关闭了连接。")
    finally:
        # 确保与当前客户端的连接被关闭
        client_socket.close()
        print(f"与 {client_address} 的连接已关闭。")
# 理论上这行代码不会被执行到,除非有 break 语句跳出 while True
server_socket.close()

这个版本的问题

虽然这个版本可以持续为多个客户端服务,但是它是串行处理的,当服务端在为一个客户端处理数据时(recv() 正在等待数据),它会阻塞,无法接受新的客户端连接,如果某个客户端发送了数据后很久才发送下一条数据,其他客户端就必须排队等待。


最终版:多线程服务端(处理并发)

为了解决串行处理的问题,我们可以使用多线程,当服务端接受到一个新的客户端连接时,就创建一个新的线程来专门处理这个客户端的通信,而主线程则继续返回 accept(),准备接受下一个连接。

这是生产环境中最常用和最简单的并发处理模型之一。

代码示例

# server_threaded.py
import socket
import threading
# 定义一个函数,用于处理每个客户端的连接
def handle_client(client_socket, client_address):
    """处理单个客户端连接的函数"""
    print(f"[新线程] 已接受来自 {client_address} 的连接!")
    try:
        while True:
            # 接收数据
            data = client_socket.recv(1024)
            if not data:
                # recv() 返回空数据,表示客户端已关闭连接
                print(f"[新线程] {client_address} 已关闭连接。")
                break
            print(f"[新线程] 收到来自 {client_address} 的数据: {data.decode('utf-8')}")
            # 发送回复
            response = f"你好,{client_address[0]}!你的消息已收到。"
            client_socket.sendall(response.encode('utf-8'))
    except ConnectionResetError:
        print(f"[新线程] 客户端 {client_address} 强制关闭了连接。")
    finally:
        # 确保与当前客户端的连接被关闭
        client_socket.close()
        print(f"[新线程] 与 {client_address} 的连接已关闭。")
# --- 主程序 ---
# 创建 socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 8888))
server_socket.listen(5)
print(f"服务器已启动,正在监听 0.0.0.0:8888...")
# 循环接受连接
while True:
    # 接受新的客户端连接
    client_socket, client_address = server_socket.accept()
    # 创建一个新线程来处理这个客户端
    # target 指定线程要执行的函数
    # args 指定传递给函数的参数
    client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address))
    # 设置为守护线程(可选)
    # 当主线程退出时,所有守护线程也会随之退出
    # client_thread.daemon = True 
    # 启动线程
    client_thread.start()
    print(f"[主线程] 已为 {client_address} 创建线程,当前活动线程数: {threading.active_count() - 1}") # -1 是因为主线程本身
# 理论上这行代码不会被执行到
server_socket.close()

如何测试多线程

  1. 运行 server_threaded.py
  2. 打开三个或更多个终端,分别用 telnetnc 连接到 0.0.1 8888
  3. 在每个客户端终端中输入不同的消息,"Client 1", "Client 2"。
  4. 观察服务端终端的输出,你会发现它能够同时打印出来自不同客户端的消息,并且每个客户端都收到了正确的回复,你会看到线程数在增加。

步骤 函数 说明
创建 socket.socket(family, type) 创建一个 socket 对象,指定地址族(如 AF_INET for IPv4)和类型(如 SOCK_STREAM for TCP)。
绑定 socket.bind((host, port)) 将 socket 绑定到一个具体的 IP 地址和端口号,服务端必须做这一步。
监听 socket.listen(backlog) 使 socket 进入监听状态,准备接受客户端连接。backlog 是连接队列的最大长度。
接受 socket.accept() 阻塞式地等待并接受一个客户端连接,成功后,返回一个新的 socket(用于与该客户端通信)和客户端的地址元组。
通信 socket.recv(bufsize) 从已连接的 socket 接收数据,bufsize 指定每次接收的最大字节数。
socket.sendall(data) 发送数据,它会持续发送数据,直到所有数据都发送完毕,适用于确保数据完整发送。
socket.send(data) 发送数据,但不保证所有数据都能发送完。
关闭 socket.close() 关闭 socket,释放资源。

注意事项

  • 端口号:端口号范围是 0-65535,0-1023 是系统保留端口,通常需要管理员权限才能使用,建议使用 1024 以上的端口号。
  • IP 地址'127.0.0.1' 是本地回环地址,只在本地机器内有效。'0.0.0.0' 表示监听本机所有网络接口,其他机器可以通过服务器的真实 IP 地址来连接。
  • 阻塞accept()recv() 都是阻塞函数,在没有连接或数据时,程序会暂停在这些地方,直到有事件发生。
  • 编码send()recv() 处理的是字节流(bytes),如果需要发送字符串,必须先将其编码(如 encode('utf-8')),接收时再解码(如 decode('utf-8'))。
  • 并发模型:除了多线程,还有多进程、异步 I/O(asyncio)等更高级的并发模型来处理大量客户端连接,对于初学者,多线程是最容易理解和实现的。
分享:
扫描分享到社交APP
上一篇
下一篇