首页 > 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视频