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
#!/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)客户端
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
#!/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)关闭两个客户端的连接,会在服务端上显示谁退出了“聊天室”。

在这里插入图片描述