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+端口号区分)。
(2)三个客户端分别在各自的窗口输入,会在服务端及其他客户端上显示(并指出谁发出的消息)。
(3)关闭两个客户端的连接,会在服务端上显示谁退出了“聊天室”。