0x01 分析

1. 原理

(1)在服务端,由于 socket 的 recv() 方法在成功读取到数据之前,线程会被阻塞,程序无法继续执行,因此需要为每个 socket 单独启动一个线程,每个线程负责与一个客户端进行通信。

(2)在客户端,从服务段读取数据的线程同样也会被阻塞,因此也需要单独启动一个线程,专门负责从服务端读取数据。

2. 实现
(1)服务端
  • 包含多个线程,每个socket对用一个线程,负责从socket中读取数据,打印出来并广播给所有建立通信的客户端,因此需要一个 socket_list 来保存所有建立连接的客户端。
  • read_client() 函数用来不断读取客户端发送的数据,同时捕获异常。
  • server_target() 函数作为线程的执行体,采用循环不断地从socket中读取客户端发送过来的数据,然后遍历 socket_list 向每个客户端进行广播。
  • 主线程则负责建立通信,同时每当与一个客户端建立通信,就将其加入到 socket_list 中,并启动一个线程为其服务。
(2)客户端
  • 每个客户端需要两个线程,子线程的执行体read_server() 函数负责不断的读取来自服务端的数据,并打印出来。
  • 主线程负责建立通信,同时接收用户在键盘的输入,将其发送给服务端。


0x02 代码

(1)服务端
#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
# 文件名:server.py

import socket
import threading

socket_list = []
s = socket.socket()
s.bind(('10.3.98.118', 30000))
s.listen()

def read_client(s):
    try:
        # 接收客户端的数据
        return s.recv(2048).decode('utf-8')
    except:
        # 若有异常,说明连接失败,则删除该socket
        print(str(addr) + ' Left!')
        socket_list.remove(s)

def socket_target(s):
    try:
        while True:
            content = read_client(s)
            if content is None:
                break
            else:
                print(content)
            # 将一个客户端发送过来的数据广播给其他客户端
            for client in socket_list:
                client.send((str(addr)+ ' say: ' +content).encode('utf-8'))
    except:
        print('Error!')
        

while True:
    conn, addr = s.accept()
    # 每当有客户连接后,就将其加到socket列表中
    socket_list.append(conn)
    print(str(addr) + ' Joined!')
    # 每当有客户连接后,就启动一个线程为其服务
    threading.Thread(target=socket_target, args=(conn,)).start()
(2)客户端
#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
# 文件名:client.py

import socket
import threading

s = socket.socket()
s.connect(('10.3.98.118', 30000))

def read_server(s):
    while True:
        # 子线程负责从服务端接受数据并打印
        content = s.recv(2048).decode('utf-8')
        print(content)
        
threading.Thread(target=read_server, args=(s,)).start()

while True:
    line = input('')
    if line == 'exit':
        break
    # 主线程负责将用户输入的数据发送到socket中
    s.send(line.encode('utf-8'))



0x03 运行结果

(1)运行一个服务端,三个客户端,在服务端可以看到哪三个客户端进入了“聊天室”(这里用ip+端口号区分)。

python tcp recv 超时 python socket recv函数_数据

(2)三个客户端分别在各自的窗口输入,会在服务端及其他客户端上显示(并指出谁发出的消息)。

python tcp recv 超时 python socket recv函数_python tcp recv 超时_02

(3)关闭两个客户端的连接,会在服务端上显示谁退出了“聊天室”。

python tcp recv 超时 python socket recv函数_python tcp recv 超时_03