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)服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
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: 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_list.append(conn) print(str(addr) + ' Joined!') threading.Thread(target=socket_target, args=(conn,)).start()
|
(2)客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
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 s.send(line.encode('utf-8'))
|
0x03 运行结果
(1)运行一个服务端,三个客户端,在服务端可以看到哪三个客户端进入了“聊天室”(这里用ip+端口号区分)。
(2)三个客户端分别在各自的窗口输入,会在服务端及其他客户端上显示(并指出谁发出的消息)。
(3)关闭两个客户端的连接,会在服务端上显示谁退出了“聊天室”。