Python -【Socket select】基本使用介绍

一. 前言

在Python中,select 是一个用于异步I/O多路复用的模块。它提供了一种简单的方法,用于监视多个文件描述符(file descriptor),以确定其中哪些文件描述符已经就绪可读、可写或者发生了异常。使用 select 模块可以实现一些高效的网络编程,比如在服务端同时监听多个客户端连接,或者在客户端同时连接多个服务端。

二. Select介绍

select 模块暴露了3个主要的函数,分别是 select.select()、select.poll() 和 select.epoll(),它们可以实现不同的多路复用机制。

三个函数的简要介绍:

  • select.select(rlist, wlist, xlist, timeout): 用于监视文件描述符的变化,可以监视读、写、异常事件。当这些事件中的任何一个发生时,select函数会返回。rlist、wlist和xlist分别是要监视的可读、可写和异常的文件描述符列表,timeout是超时参数,单位是秒。
  • select.poll():使用场景跟 select.select() 相同,但性能更好,适用于监视较大量的文件描述符。
  • select.epoll(): Linux系统下的I/O复用机制,也是一种性能很好的多路复用机制。它相比于 select.poll()
    的优点主要在于它可以支持更多的连接数。

除了以上三种函数,还有 select.kevent() 和 select.kqueue() 函数,它们适用于FreeBSD系统。

在使用 select 模块时,需要注意以下几点:

  • 最好使用非阻塞的 socket,以避免程序在等待 socket 数据时被阻塞,从而可以处理其它 socket 数据。
  • select 函数可能会有一些性能问题,当需要同时监听大量的文件描述符时,可能会导致 CPU 占用过高,所以使用时需要注意调优。

三. 代码示例

下面给出一个简单的示例,演示如何使用 select 模块同时监听多个 socket:

服务端Socket

import socket
import select# 设置需要监听的 socket 地址和端口
ADDRESS = ("localhost", 9000)# 创建一个服务器 socket,并绑定到指定地址和端口
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(ADDRESS)
server_socket.listen(5)# 将 server_socket 设置为非阻塞模式
server_socket.setblocking(False)# 存储已连接的 client_socket
connected_clients = []# 开始监听
while True:# 使用 select 函数监听所有连接的 client_socketreadable_sockets, _, _ = select.select([server_socket] + connected_clients,[], [], 1)# 处理所有可读的 socketfor sock in readable_sockets:# 如果是 server_socket 表示有新的连接if sock is server_socket:client_socket, client_address = server_socket.accept()connected_clients.append(client_socket)print(f"New client connected: {client_address}")# 否则是已连接的 client_socket,需要处理收到的数据else:try:data = sock.recv(1024)if data:print(f"Received data from {sock.getpeername()}: {data.decode()}")else:# 如果收到的数据为空,表示连接已经断开,需要关闭 socket 并从 connected_clients 中移除sock.close()print(f"Client {sock.getpeername()} disconnected")connected_clients.remove(sock)except Exception as e:# 出现异常,也需要关闭 socket 并从 connected_clients 中移除print(f"Error occurred while receiving data from {sock.getpeername()}: {e}")sock.close()connected_clients.remove(sock)

在这个示例中,我们使用 select 模块同时监听 server_socket 和所有 connected_clients,当有新的 client_socket 连接时,会将其添加到 connected_clients 列表中;当存在可读的 socket 时,会根据是 server_socket 还是 client_socket 处理它们的相关操作。

需要注意的是,在处理已断开连接的 client_socket 时,需要将其关闭并从 connected_clients 中移除,否则会一直存在于 connected_clients 中,导致程序出现意料之外的错误。

客户端Socket

import socket
import select
import sys# 创建5个socket对象,用于连接5个不同的服务端
s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s3 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s4 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s5 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 设置服务端地址和端口号
server1_address = ('localhost', 9000)
server2_address = ('localhost', 9001)
server3_address = ('localhost', 9002)
server4_address = ('localhost', 9003)
server5_address = ('localhost', 9004)# 将socket对象添加到列表中
sockets = [s1, s2, s3, s4, s5]# 连接服务端
for sock, address in zip(sockets, [server1_address, server2_address, server3_address, server4_address, server5_address]):try:sock.connect(address)except Exception as e:print(f"Exception: {e}")sys.exit()while True:# 使用select函数监控所有socket对象ready_to_read, _, _ = select.select(sockets, [], [])# 在任何一个可读socket对象中读取数据for sock in ready_to_read:try:data = sock.recv(1024)if data:print(f"Received: {data}")else:socket.close()sockets.remove(sock)except Exception as e:sockets.remove(sock)print(f"Exception: {e}")sock.close()

我们使用select函数监视所有socket对象。如果有数据可读,我们就会读取数据并打印出来。如果在接收数据时发生异常,我们将从sockets列表中删除该socket对象并关闭它。

四. 多个服务端和多个客户端使用select代码示例

服务端

使用多线程启动多个服务端
下面是服务端代码的示例,它会同时监听8001, 8002和8003端口。当客户端连接成功后会向客户端发送一条欢迎消息,当客户端发送信息时会原样返回给客户端,当客户端关闭连接时,服务器也会关闭相应的socket

import socket
import threading# 设置服务端地址和端口号
SERVER_ADDRESS = "localhost"
SERVER_PORT = 8001# 监听的队列大小
LISTEN_QUEUE_SIZE = 5# 消息欢迎消息
WELCOME_MESSAGE = "Welcome to server!"# 为每个客户端建立相应的socket连接
def handle_client(client_socket, client_address):print(f"New connection from {client_address}")client_socket.send(WELCOME_MESSAGE.encode())while True:try:data = client_socket.recv(1024)if data:print(f"Received message from {client_address}: {data.decode()}")client_socket.send(data)else:# 关闭客户端连接client_socket.close()print(f"Connection closed by {client_address}")breakexcept Exception as e:print(f"Error encountered while receiving data from {client_address}: {e}")client_socket.close()break# 监听多个socket
def listen(address, connections):# 创建socket连接server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)server_socket.bind(address)server_socket.listen(LISTEN_QUEUE_SIZE)print(f"Listening on {address[0]}:{address[1]}")while True:# 等待客户端连接client_socket, client_address = server_socket.accept()print(f"Accepted new connection from {client_address}")# 为客户端启动线程thread = threading.Thread(target=handle_client, args=(client_socket, client_address))thread.daemon = Truethread.start()# 将客户端的socket连接保存connections.append(client_socket)# 启动监听
connections = []
threads = []
for port in range(SERVER_PORT, SERVER_PORT + 3):address = (SERVER_ADDRESS, port)thread = threading.Thread(target=listen, args=(address, connections))threads.append(thread)thread.start()# 等待所有线程结束
for thread in threads:thread.join()# 关闭所有连接
for connection in connections:connection.close()

在这个示例代码中,我们使用了Python中的多线程来同时监听8001, 8002和8003端口的客户端连接,这个方式更加灵活和高效。handle_client()函数用于处理每个客户端连接,它会向客户端发送一条欢迎消息,并在客户端发送数据时原样返回给客户端。listen()函数用于监听一个端口并为每个客户端连接启动一个新线程处理。最后在主线程中,我们启动了三个线程分别监听不同的端口,等待所有线程结束并关闭所有连接。

客户端

下面的客户端程序可以连接多个服务端并同时监听每个服务端返回的消息。代码中处理了连接、发送和接收数据时可能出现的异常情况。

import socket
import time
import tracebackimport select# 设置服务端地址列表
SERVER_ADDRESSES = [("localhost", 8001), ("localhost", 8002), ("localhost", 8003)]def set_socket(server_address):sockets = []for server_addr in server_address:client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client_socket.setblocking(True)sockets.append(client_socket)return socketsdef start_client(server_address):sockets = set_socket(server_address)# 连接服务端for index, client_socket in enumerate(sockets):server_address = SERVER_ADDRESSES[index]try:client_socket.connect(server_address)except Exception as e:print(f"Connect to {server_address} failed: {e}")start_listen(sockets)def start_listen(sockets):# 使用select监听服务端while True:try:# 仅监听已连接的socket,获取到了数据则接收数据readable, writeable, errors = select.select(sockets, [], sockets)for socket in readable:try:data = socket.recv(1024)if data:print(f"Received message from {socket.getpeername()}: {data.decode()}")else:# 当对端关闭连接时,对应的可读socket也会被认为是可写的# 并且其recv方法将返回空字节流sockets.remove(socket)except Exception as e:print(f"Error encountered while receiving data: {e}")sockets.remove(socket)for socket in errors:print(f"Error encountered on {socket.getpeername()}")sockets.remove(socket)except Exception as e:traceback.print_exc()time.sleep(1)start_client(SERVER_ADDRESSES)breakif __name__ == '__main__':# 创建socket连接start_client(SERVER_ADDRESSES)

以上就是关于【python socket select】的相关介绍,希望可以对你有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/63167.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SPSS统计作图教程:频率多边形

SPSS统计作图教程:频率多边形 1、问题与数据 某研究者想了解某数据集中最大携氧能力(VO2max)是否服从正态分布,部分数据如图1。研究者应如何绘图查看呢? 图1 部分数据 2、对问题的分析 研究者想绘图展示最大携氧能…

深入理解 JVM 之——Java 内存区域与溢出异常

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 本篇为深入理解 Java 虚拟机第二章内容,推荐在学习前先掌握基础的 Linux 操作、编译原理、计算机组成原理等计算机基础以及扎实的 C/C 功底。 该系列的 GitHub 仓库:https://github…

go学习part20(1)反射

283_尚硅谷_反射基本介绍和示意图_哔哩哔哩_bilibili 1.介绍 1)基本数据类型的类型和类别一致,但是结构体等不一样。 2)反射的例子(桥连接,序列化) 序列化指定tag,会反射生成tag字符串 3)refl…

Vue在表格中拿到该行信息的方式(作用域插槽-#default-scope-解决按钮与行点击的顺序问题)

遇到的问题 在做表格的时候,表格是封装好了的,用于展示数据。如果想给单行增加按钮,可以单独写一列存放按钮,最基本的需求是,点击按钮后要拿到数据然后发起请求。 且Vue的element-plus,当我们点击按钮之后…

Linux挖矿程序清除

1. 找到挖矿进程 2.找到病毒的文件地址 ls -l /proc/进程ID/exe3.删除文件命令 rm -rf 文件地址4.杀死挖矿进程 kill -9 进程ID

11 mysql float/double/decimal 的数据存储

前言 这里主要是 由于之前的一个 datetime 存储的时间 导致的问题的衍生出来的探究 探究的主要内容为 int 类类型的存储, 浮点类类型的存储, char 类类型的存储, blob 类类型的存储, enum/json/set/bit 类类型的存储 本文主要 的相关内容是 float, decimal 类类型的相关数据…

15. 卡牌游戏

目录 题目 思路 C整体代码(含详细注释) 题目 Description 小张在玩一种卡牌游戏,牌组由张牌组成,其中张上写有数字各一张,其余张上全部是数字。 现在牌组经过随机打乱后,小张拿走其中张牌作为手牌&#…

【方案】基于视频与AI智能分析技术的城市轨道交通视频监控建设方案

一、背景分析 地铁作为重要的公共场所交通枢纽,流动性非常高、人员大量聚集,轨道交通需要利用视频监控系统来实现全程、全方位的安全防范,这也是保证地铁行车组织和安全的重要手段。调度员和车站值班员通过系统监管列车运行、客流情况、变电…

【Java List与数组】List<T>数组和数组List<T>的区别(124)

List数组:存储List的数组,即:数组中的元素是:List; 数组List:存储数组的List,即:List中的数据是类型的数组; 测试案例: import java.util.ArrayList; impor…

中国建筑出版传媒许少辉博士八一新书乡村振兴战略下传统村落文化旅游设计日京东当当畅销榜自由营九三学

中国建筑出版传媒许少辉博士八一新书乡村振兴战略下传统村落文化旅游设计日京东当当畅销榜自由营九三学

【JavaSE】String类

两种创建String对象的区别 String s1 "hello"; String s2 new String("hello");s1是先查看常量池是否有 “hello” 数据空间,如果有就直接指向它,如果没有就创建然后指向它。s1最终指向的是常量池的空间地址。 s2是先在堆中创建空…

MySQL数据库——多表查询(3)-自连接、联合查询、子查询

目录 自连接 查询语法 自连接演示 联合查询 查询语法 子查询 介绍 标量子查询 列子查询 行子查询 表子查询 自连接 通过前面的学习,我们对于连接已经有了一定的理解。而自连接,通俗地去理解就是自己连接自己,即一张表查询多次。…

XSS攻击

目录 什么是XSS脚本攻击XSS攻击的本质XSS 攻击分类存储型 XSS 的攻击步骤:反射型 XSS 的攻击步骤:DOM 型 XSS 的攻击步骤: 前端处理后端处理参考资料 随着互联网的高速发展 信息安全问题已经成为企业最为关注的焦点之一,而前端又…

【electron】Puppeteer 和 Electron 共用同一个Chrome 或 Chromium浏览器二进制文件

将 Puppeteer 的可执行路径设置为 Electron 的可执行路径来实现这一点 以下是一个示例代码,展示了如何在 Puppeteer 中使用 Electron 的浏览器二进制文件: const puppeteer require(puppeteer-core);(async () > {// 设置 Electron 的可执行路径co…

visual studio编写DLL,python调用

选择第一个c DLL&#xff0c; 然后项目源文件下右击新建项&#xff0c;这里名字随便取&#xff0c;在代码中输入一下内容&#xff1a; #include <iostream>#define EXPORT extern "C" __declspec(dllexport)EXPORT int sub(int a, int b) {return a - b; } 在…

SpringBoot项目配置文件数据库用户名密码加密

1、需求 在使用SpringBoot开发过程中&#xff0c;会将一些敏感信息配置到SpringBoot项目的配置文件中(不考虑使用配置中心的情况 )&#xff0c;例如数据库的用户名和密码、Redis的密码等。为了保证敏感信息的安全&#xff0c;我们需要将此类数据进行加密配置。 2、操作步骤 …

Vue 纯 css 编写鱼骨图

Vue 纯 css 编写鱼骨图 参考文章1忘记 参考文章1会点 php 的前端小渣渣 &#xff08;我是在此基础上进行二改的&#xff09; 二改组件 粘贴下来到手直接用。 <template><div class"fishbone scroll"><div class"content"><el-row typ…

网络编程套接字(3): 简单的TCP网络程序

文章目录 网络编程套接字(3)4. 简单的TCP网络程序4.1 服务端创建(1) 创建套接字(2) 绑定端口(3) 监听(4) 获取新连接(5) 处理读取与写入 4.2 客户端创建(1)连接服务器 4.3 代码编写(1) v1__简单发送消息(2) v2_多进程版本(3) v3_多线程版本(4) v4_线程池版本 网络编程套接字(3)…

leetcode原题: 生存人数

题目&#xff1a; 给定 N 个人的出生年份和死亡年份&#xff0c;第 i 个人的出生年份为 birth[i]&#xff0c;死亡年份为 death[i]&#xff0c;实现一个方法以计算生存人数最多的年份。 你可以假设所有人都出生于 1900 年至 2000 年&#xff08;含 1900 和 2000 &#xff09;…

【位运算】leetcode面试题:消失的两个数字

一.题目描述 消失的两个数字 二.思路分析 本题难度标签是困难&#xff0c;但实际上有了只出现一次的数字iii这道题的铺垫&#xff0c;本题的思路还是很容易想到的。 温馨提示&#xff1a;阅读本文前可以先查看我的【位运算】专栏的第一篇文章&#xff0c;其中包含位运算这类…