python中select用法_Python select及selectors模块概念用法详解

1. select模块

针对select,要先理解其他几个概念:

文件描述符:

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

内核空间:

Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G。Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。

内核空间和用户空间一般通过系统调用进行通信。

select就是针对许多文件描述符(简称fd)进行监控,它有三个参数:

rlist -- wait until ready for reading

wlist -- wait until ready for writing

xlist -- wait for an "exceptional condition"

第一个参数监控 进来的 数据的fd列表,select监控这个列表,等待这些fd发送过来数据,一旦数据发送过来了(可以读取了),就返回一个可读的fd列表

第二个参数监控 出去的 数据的fd列表,select监控这个列表,等待这些fd发送出去数据,一旦fd准备好发送了(可以写入了),就返回一个可写的fd列表

第三个参数监控fd列表,返回出异常的fd列表

服务端:

import select

import socket

import sys

import queue

# 生成socket对象

server = socket.socket()

# 设置非阻塞模式

server.setblocking(False)

# 绑定地址,设置监听

server.bind(("localhost",9999))

server.listen(5)

# 将自己也放进待监测列表里

inputs = [server, ]

outputs = []

message_queues = {}

while True:

"""

关于socket可读可写的判断,可以参考博客:https://blog.csdn.net/majianfei1023/article/details/45788591

"""

rlist, wlist, elist = select.select(inputs,outputs,inputs) #如果没有任何fd就绪,那程序就会一直阻塞在这里

for r in rlist: # 遍历已经可以准备读取数据的 fd

if r is server: # 如果这个 fd 是server,即 server 有数据待接收读取,说明有新的客户端连接过来了

conn, client_addr = r.accept()

print("new connection from",client_addr)

conn.setblocking(False)

inputs.append(conn) # 将这个新的客户端连接添加到检测的列表中

message_queues[conn] = queue.Queue() # 用队列存储客户端发送来的数据,等待服务器统一返回数据

else: # 这个可读的 r 不是服务器,那就是某个客户端。就是说客户端发送数据过来了,这些数据处于待读取状态

try: # 异常处理,这是为了防止客户端异常断开报错(比如手动关掉客户端黑窗口,服务器也会跟着报错退出)

data = r.recv(1024)

if data: # 根据判断data是否为空,判断客户端是否断开

print("收到来自[%s]的数据:" % r.getpeername()[0], data)

message_queues[r].put(data) # 收到的数据先放到queue里,一会返回给客户端

if r not in outputs:

outputs.append(r) # 放进可写的fd列表中,表明这些 fd 已经准备好去发送数据了。

else: # 如果数据为空,表明客户端断开了

print("客户端断开了")

if r in outputs:

outputs.remove(r) # 清理已断开的连接

inputs.remove(r) # 清理已断开的连接

del message_queues[r] # 清理已断开的连接

except ConnectionResetError: # 如果报错,说明客户端断开了

print("客户端异常断开了", r)

if r in outputs:

outputs.remove(r) # 清理已断开的连接

inputs.remove(r) # 清理已断开的连接

del message_queues[r] # 清理已断开的连接

for w in wlist: # 遍历可写的 fd 列表,即准备好发送数据的那些fd

# 判断队列是否为空

try :

next_msg = message_queues[w].get_nowait()

except queue.Empty:

# print("client [%s]" % w.getpeername()[0], "queue is empty..")

outputs.remove(w)

# 队列不为空,就把队列中的数据改成大写,原样发回去

else:

# print("sending msg to [%s]"% w.getpeername()[0], next_msg)

w.send(next_msg.upper())

for e in elist: # 处理报错的 fd

e.close()

print("Error occured in ",e.getpeername())

inputs.remove(e)

if e in outputs:

outputs.remove(e)

del message_queues[e]

客户端:

import socket

import sys

sock = socket.socket()

sock.connect(("localhost",9999))

while True:

c = input(">>>:").strip()

sock.send(c.encode())

data = sock.recv(1024)

print(data.decode())

sock.close()

2. selectors模块

官方文档:https://docs.python.org/3/library/selectors.html

服务端:

import selectors

import socket

# 根据平台自动选择最佳的IO多路机制,比如linux就会选择epoll,windows会选择select

sel = selectors.DefaultSelector()

def accept(sock, mask):

# 建立客户端连接

conn, addr = sock.accept()

print("accepted", conn, "from", addr)

# 设置非阻塞模式

conn.setblocking(False)

# 再次注册一个连接,将其加入监测列表中,

sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):

try: # 抛出客户端强制关闭的异常(如手动关闭客户端黑窗口)

data = conn.recv(1000) # Should be ready

if data:

print("echoing", repr(data), "to", conn)

conn.send(data) # Hope it won"t block

else:

print("Client closed.", conn)

# 将conn从监测列表删除

sel.unregister(conn)

conn.close()

except ConnectionResetError:

print("Client forcibly closed.", conn)

# 将conn从监测列表删除

sel.unregister(conn)

conn.close()

# 创建socket对象

sock = socket.socket()

# 绑定端口,设置监听

sock.bind(("localhost", 1234))

sock.listen(100)

# 设置为非阻塞模式

sock.setblocking(False)

# 注册一个文件对象,监测它的IO事件,data是和文件对象相关的数据(此处放置了一个 accept 函数的内存地址)

# register(fileobj, events, data=None)

sel.register(sock, selectors.EVENT_READ, accept)

while True:

"""

sel.select()

看似是select方法,实际上会根据平台自动选择使用select还是epoll

它返回一个(key, events)元组, key是一个namedtuple类型的元组,可以使用 key.name 获取元组的数据

key 的内容(fileobj,fd,events,data):

fileobj 已经注册的文件对象

fd 也就是第一个参数的那个文件对象的更底层的文件描述符

events 等待的IO事件

data 可选项。可以存一些和fileobj有关的数据,如 sessioin 的 id

"""

events = sel.select() # 监测有无活动对象,没有就阻塞在这里等待

for key, mask in events: # 有活动对象了

callback = key.data # key.data 是注册时传递的 accept 函数

callback(key.fileobj, mask) # key.fileobj 就是传递的 socket 对象

客户端:

import socket

tin=socket.socket()

tin.connect(("localhost",1234))

while True:

inp=input(">>>>")

tin.send(inp.encode("utf8"))

data=tin.recv(1024)

print(data.decode("utf8"))

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持云海天教程。

原文链接:https://www.cnblogs.com/wztshine/p/12091062.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Python ,实现 Socket 的高并发可以使用多种方式,其 `select` 和 `selectors` 是比较常用的两种方式。 1. 使用 `select` 实现 Socket 并发 使用 `select` 实现 Socket 的高并发,可以按照以下步骤进行: 1) 创建一个 Socket,设置为非阻塞模式,如下所示: ``` import socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setblocking(False) ``` 2) 绑定 Socket 地址和端口,并开始监听,如下所示: ``` server_socket.bind(('localhost', 8000)) server_socket.listen(128) ``` 3) 创建一个 `select` 对象,并将监听的 Socket 加入到该对象,如下所示: ``` import select rlist = [server_socket] wlist = [] xlist = [] while True: rs, ws, xs = select.select(rlist, wlist, xlist) for r in rs: if r is server_socket: conn, addr = server_socket.accept() conn.setblocking(False) rlist.append(conn) else: data = r.recv(1024) if data: # 处理接收到的数据 else: r.close() rlist.remove(r) ``` 4) 在 `select` 对象的 `select()` 方法,使用 `rlist` 参数来监听读事件,使用 `wlist` 参数来监听写事件,使用 `xlist` 参数来监听异常事件。如果有 Socket 可读、可写或者出现异常,`select()` 方法就会返回对应的列表。 5) 在返回的可读列表,如果是监听的 Server Socket,说明有新的连接请求,需要调用 `accept()` 方法接收连接,并将新的 Socket 加入到 `rlist` 列表;如果是普通的 Socket,说明有数据到来,需要调用 `recv()` 方法接收数据,并进行相应的处理。 2. 使用 `selectors` 实现 Socket 并发 使用 `selectors` 实现 Socket 的高并发,可以按照以下步骤进行: 1) 创建一个 Socket,并将其注册到 `selectors` 对象,如下所示: ``` import selectors import socket sel = selectors.DefaultSelector() server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 8000)) server_socket.listen(128) server_socket.setblocking(False) sel.register(server_socket, selectors.EVENT_READ, data=None) ``` 2) 在 `selectors` 对象的 `select()` 方法等待事件,如下所示: ``` while True: events = sel.select(timeout=None) for key, mask in events: if key.data is None: # 处理 Server Socket 的连接请求 else: # 处理普通 Socket 的数据读取和写入 ``` 3) 注册 Server Socket 的时候,将 `data` 参数设置为 `None`,在处理连接请求时,可以将 `data` 参数设置为新连接的 Socket 对象;在处理普通 Socket 的数据读取和写入时,可以通过 `key.data` 获取到该 Socket 对应的数据。 4) 在处理普通 Socket 的数据读取和写入时,可以通过 `mask` 参数来判断是读事件还是写事件,如果是读事件,就调用 `recv()` 方法接收数据,如果是写事件,就调用 `send()` 方法发送数据。 以上就是使用 `select` 和 `selectors` 实现 Socket 并发的步骤,具体实现时需要根据具体情况进行修改。 ### 回答2: 在Python,要实现高并发的socket编程,可以使用selectors模块selectors模块提供了一种应对I/O多路复用的高效方式,它基于select或者epoll等系统调用,可以同时监视多个文件对象(如socket),并在有数据到来时进行处理。 首先,我们需要创建一个selectors对象,并注册要监视的socket对象。可以使用默认的selectors.DefaultSelector()来创建一个selector对象。 接下来,可以使用selectors对象的register方法来注册要监视的socket对象。register方法接受一个socket对象和一个事件类型(如selectors.EVENT_READ用于读事件),并将其添加到selector对象的监视列表。 然后,可以使用selectors对象的select方法来等待事件的发生。select方法会一直阻塞直到有事件发生,并返回一个事件列表。 最后,可以遍历事件列表,根据事件的类型来处理不同的操作。例如,如果事件类型为selectors.EVENT_READ,则可以调用socket对象的recv方法来接收数据。 使用selectors模块能够实现高并发的socket编程,可以提高程序的性能和效率。同时,selectors模块还提供了一些其他功能,比如超时处理、取消注册等,可以根据实际需要进行使用。 总之,selectors模块Python实现socket并发的一种高效方式,通过使用其提供的方法和功能,可以轻松实现高并发的socket编程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值