首页 > Python基础教程 > Python网络编程
阅读:4,222
Python多线程实现socket通信
前面章节对服务器端和客户端只是进行了简单的通信操作,即服务器端接受客户端的连接之后,向客户端输出一个字符串,而客户端也只是读取服务器端的字符串后就退出了。
在实际应用中,客户端则可能需要和服务器端保持长时间通信,即服务器端需要不断地读取客户端数据,并向客户端写入数据;客户端也需要不断地读取服务器端数据,并向服务器端写入数据。
由于 socket 的 recv() 方法在成功读取到数据之前,线程会被阻塞,程序无法继续执行。考虑到这个原因,服务器端应该为每个 socket 都单独启动一个线程,每个线程负责与一个客户端进行通信。
客户端读取服务器端数据的线程同样会被阻塞,所以系统应该单独启动一个线程,该线程专门负责读取服务器端数据。
现在考虑实现一个命令行界面的 C/S 聊天室应用,服务器端应该包含多个线程,每个 socket 对应一个线程,该线程负责从 socket 中读取数据(从客户端发送过来的数据),并将所读取到的数据向每个 socket 发送一次(将一个客户端发送过来的数据“广播”给其他客户端),因此需要在服务器端使用 list 来保存所有的 socket。
下面是服务器端的实现代码。该服务器端代码定义了一个 server_target() 函数,该函数将会作为线程执行的 target,负责处理每个 socket 的数据通信。
代表服务器端线程执行体的 server_target() 函数则不断地读取客户端数据。程序使用 read_from_client() 函数来读取客户端数据,如果在读取数据过程中捕获到异常,则表明该 socket 对应的客户端 socket 出现了问题(到底什么问题不用深究,反正不正常),程序就将该 socket 从 socket_list 列表中删除,如 read_from_client() 函数中的 ① 号代码所示。
当服务器端线程读取到客户端数据之后,程序遍历 socket_list 列表,并将该数据向 socket_list 列表中的每个 socket 发送一次(该服务器端线程把从 socket 中读取到的数据向 socket_list 列表中的每个 socket 转发一次),如 server_target() 函数中的代码所示。
每个客户端都应该包含两个线程,其中一个负责读取用户的键盘输入内容,并将用户输入的数据输出到 socket 中,另一个负责读取 socket 中的数据(从服务器端发送过来的数据),并将这些数据打印输出。由程序的主线程负责读取用户的键盘输入内容,由新线程负责读取 socket 数据。
此外,当主线程的 socket 连接到服务器端之后,以 read_from_server() 函数为 target 启动了新线程来处理 socket 通信,如程序中 ① 号代码所示。read_from_server() 函数使用死循环读取 socket 中的数据(就是来自服务器端的数据),并将这些内容在控制台打印出来,如 read_from_server() 函数中的代码所示。
先运行上面的 MyServer 程序,该程序运行后只是作为服务器,看不到任何输出信息。再运行多个 MyClient 程序(相当于启动多个聊天室客户端登录该服务器),接下来可以在任何一个客户端通过键盘输入一些内容,然后按回车键,即可在所有客户端(包括自己)的控制台中收到刚刚输入的内容,这就粗略地实现了一个 C/S 结构的聊天室应用。
在实际应用中,客户端则可能需要和服务器端保持长时间通信,即服务器端需要不断地读取客户端数据,并向客户端写入数据;客户端也需要不断地读取服务器端数据,并向服务器端写入数据。
由于 socket 的 recv() 方法在成功读取到数据之前,线程会被阻塞,程序无法继续执行。考虑到这个原因,服务器端应该为每个 socket 都单独启动一个线程,每个线程负责与一个客户端进行通信。
客户端读取服务器端数据的线程同样会被阻塞,所以系统应该单独启动一个线程,该线程专门负责读取服务器端数据。
现在考虑实现一个命令行界面的 C/S 聊天室应用,服务器端应该包含多个线程,每个 socket 对应一个线程,该线程负责从 socket 中读取数据(从客户端发送过来的数据),并将所读取到的数据向每个 socket 发送一次(将一个客户端发送过来的数据“广播”给其他客户端),因此需要在服务器端使用 list 来保存所有的 socket。
下面是服务器端的实现代码。该服务器端代码定义了一个 server_target() 函数,该函数将会作为线程执行的 target,负责处理每个 socket 的数据通信。
import socket
import threading
# 定义保存所有socket的列表
socket_list = []
# 创建socket对象
ss = socket.socket()
# 将socket绑定到本机IP和端口
ss.bind(('192.168.1.88', 30000))
# 服务端开始监听来自客户端的连接
ss.listen()
def read_from_client(s):
try:
return s.recv(2048).decode('utf-8')
# 如果捕获到异常,则表明该socket对应的客户端已经关闭
except:
# 删除该socket
socket_list.remove(s); # ①
def server_target(s):
try:
# 采用循环不断地从socket中读取客户端发送过来的数据
while True:
content = read_from_client(s)
print(content)
if content is None:
break
for client_s in socket_list:
client_s.send(content.encode('utf-8'))
except e:
print(e.strerror)
while True:
# 此行代码会阻塞,将一直等待别人的连接
s, addr = ss.accept()
socket_list.append(s)
# 每当客户端连接后启动一个线程为该客户端服务
threading.Thread(target=server_target, args=(s, )).start()
上面实现的服务器端主程序只负责接收客户端 socket 的连接请求,每当客户端 socket 连接进来之后,程序都将对应的 socket 加入 socket_list 列表中保存,并为该 socket 启动一个线程,该线程负责处理该 socket 所有的通信任务,如程序中最后 3 行代码所示。代表服务器端线程执行体的 server_target() 函数则不断地读取客户端数据。程序使用 read_from_client() 函数来读取客户端数据,如果在读取数据过程中捕获到异常,则表明该 socket 对应的客户端 socket 出现了问题(到底什么问题不用深究,反正不正常),程序就将该 socket 从 socket_list 列表中删除,如 read_from_client() 函数中的 ① 号代码所示。
当服务器端线程读取到客户端数据之后,程序遍历 socket_list 列表,并将该数据向 socket_list 列表中的每个 socket 发送一次(该服务器端线程把从 socket 中读取到的数据向 socket_list 列表中的每个 socket 转发一次),如 server_target() 函数中的代码所示。
每个客户端都应该包含两个线程,其中一个负责读取用户的键盘输入内容,并将用户输入的数据输出到 socket 中,另一个负责读取 socket 中的数据(从服务器端发送过来的数据),并将这些数据打印输出。由程序的主线程负责读取用户的键盘输入内容,由新线程负责读取 socket 数据。
import socket
import threading
# 创建socket对象
s = socket.socket()
# 连接远程主机
s.connect(('192.168.1.88', 30000))
def read_from_server(s):
while True:
print(s.recv(2048).decode('utf-8'))
# 客户端启动线程不断地读取来自服务器的数据
threading.Thread(target=read_from_server, args=(s, )).start() # ①
while True:
line = input('')
if line is None or line == 'exit':
break
# 将用户的键盘输入内容写入socket
s.send(line.encode('utf-8'))
上面程序中的主线程读取到用户的键盘输入内容后,将该内容发送到 socket 中(实际上就是把数据发送给服务器端)。此外,当主线程的 socket 连接到服务器端之后,以 read_from_server() 函数为 target 启动了新线程来处理 socket 通信,如程序中 ① 号代码所示。read_from_server() 函数使用死循环读取 socket 中的数据(就是来自服务器端的数据),并将这些内容在控制台打印出来,如 read_from_server() 函数中的代码所示。
先运行上面的 MyServer 程序,该程序运行后只是作为服务器,看不到任何输出信息。再运行多个 MyClient 程序(相当于启动多个聊天室客户端登录该服务器),接下来可以在任何一个客户端通过键盘输入一些内容,然后按回车键,即可在所有客户端(包括自己)的控制台中收到刚刚输入的内容,这就粗略地实现了一个 C/S 结构的聊天室应用。
所有教程
- socket
- Python基础教程
- C#教程
- MySQL函数
- MySQL
- C语言入门
- C语言专题
- C语言编译器
- C语言编程实例
- GCC编译器
- 数据结构
- C语言项目案例
- C++教程
- OpenCV
- Qt教程
- Unity 3D教程
- UE4
- STL
- Redis
- Android教程
- JavaScript
- PHP
- Mybatis
- Spring Cloud
- Maven
- vi命令
- Spring Boot
- Spring MVC
- Hibernate
- Linux
- Linux命令
- Shell脚本
- Java教程
- 设计模式
- Spring
- Servlet
- Struts2
- Java Swing
- JSP教程
- CSS教程
- TensorFlow
- 区块链
- Go语言教程
- Docker
- 编程笔记
- 资源下载
- 关于我们
- 汇编语言
- 大数据
- 云计算
- VIP视频