python中select用法_Python select及selectors模块概念用法详解
1. select模塊
針對select,要先理解其他幾個概念:
文件描述符:
文件描述符在形式上是一個非負(fù)整數(shù)。實(shí)際上,它是一個索引值,指向內(nèi)核為每一個進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表。當(dāng)程序打開一個現(xiàn)有文件或者創(chuàng)建一個新文件時,內(nèi)核向進(jìn)程返回一個文件描述符。
內(nèi)核空間:
Linux簡化了分段機(jī)制,使得虛擬地址與線性地址總是一致,因此,Linux的虛擬地址空間也為0~4G。Linux內(nèi)核將這4G字節(jié)的空間分為兩部分。將最高的1G字節(jié)(從虛擬地址0xC0000000到0xFFFFFFFF),供內(nèi)核使用,稱為“內(nèi)核空間”。而將較低的3G字節(jié)(從虛擬地址 0x00000000到0xBFFFFFFF),供各個進(jìn)程使用,稱為“用戶空間)。因為每個進(jìn)程可以通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核,因此,Linux內(nèi)核由系統(tǒng)內(nèi)的所有進(jìn)程共享。于是,從具體進(jìn)程的角度來看,每個進(jìn)程可以擁有4G字節(jié)的虛擬空間。
內(nèi)核空間中存放的是內(nèi)核代碼和數(shù)據(jù),而進(jìn)程的用戶空間中存放的是用戶程序的代碼和數(shù)據(jù)。不管是內(nèi)核空間還是用戶空間,它們都處于虛擬空間中。
內(nèi)核空間和用戶空間一般通過系統(tǒng)調(diào)用進(jìn)行通信。
select就是針對許多文件描述符(簡稱fd)進(jìn)行監(jiān)控,它有三個參數(shù):
rlist -- wait until ready for reading
wlist -- wait until ready for writing
xlist -- wait for an "exceptional condition"
第一個參數(shù)監(jiān)控 進(jìn)來的 數(shù)據(jù)的fd列表,select監(jiān)控這個列表,等待這些fd發(fā)送過來數(shù)據(jù),一旦數(shù)據(jù)發(fā)送過來了(可以讀取了),就返回一個可讀的fd列表
第二個參數(shù)監(jiān)控 出去的 數(shù)據(jù)的fd列表,select監(jiān)控這個列表,等待這些fd發(fā)送出去數(shù)據(jù),一旦fd準(zhǔn)備好發(fā)送了(可以寫入了),就返回一個可寫的fd列表
第三個參數(shù)監(jiān)控fd列表,返回出異常的fd列表
服務(wù)端:
import select
import socket
import sys
import queue
# 生成socket對象
server = socket.socket()
# 設(shè)置非阻塞模式
server.setblocking(False)
# 綁定地址,設(shè)置監(jiān)聽
server.bind(("localhost",9999))
server.listen(5)
# 將自己也放進(jìn)待監(jiān)測列表里
inputs = [server, ]
outputs = []
message_queues = {}
while True:
"""
關(guān)于socket可讀可寫的判斷,可以參考博客:https://blog.csdn.net/majianfei1023/article/details/45788591
"""
rlist, wlist, elist = select.select(inputs,outputs,inputs) #如果沒有任何fd就緒,那程序就會一直阻塞在這里
for r in rlist: # 遍歷已經(jīng)可以準(zhǔn)備讀取數(shù)據(jù)的 fd
if r is server: # 如果這個 fd 是server,即 server 有數(shù)據(jù)待接收讀取,說明有新的客戶端連接過來了
conn, client_addr = r.accept()
print("new connection from",client_addr)
conn.setblocking(False)
inputs.append(conn) # 將這個新的客戶端連接添加到檢測的列表中
message_queues[conn] = queue.Queue() # 用隊列存儲客戶端發(fā)送來的數(shù)據(jù),等待服務(wù)器統(tǒng)一返回數(shù)據(jù)
else: # 這個可讀的 r 不是服務(wù)器,那就是某個客戶端。就是說客戶端發(fā)送數(shù)據(jù)過來了,這些數(shù)據(jù)處于待讀取狀態(tài)
try: # 異常處理,這是為了防止客戶端異常斷開報錯(比如手動關(guān)掉客戶端黑窗口,服務(wù)器也會跟著報錯退出)
data = r.recv(1024)
if data: # 根據(jù)判斷data是否為空,判斷客戶端是否斷開
print("收到來自[%s]的數(shù)據(jù):" % r.getpeername()[0], data)
message_queues[r].put(data) # 收到的數(shù)據(jù)先放到queue里,一會返回給客戶端
if r not in outputs:
outputs.append(r) # 放進(jìn)可寫的fd列表中,表明這些 fd 已經(jīng)準(zhǔn)備好去發(fā)送數(shù)據(jù)了。
else: # 如果數(shù)據(jù)為空,表明客戶端斷開了
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 列表,即準(zhǔn)備好發(fā)送數(shù)據(jù)的那些fd
# 判斷隊列是否為空
try :
next_msg = message_queues[w].get_nowait()
except queue.Empty:
# print("client [%s]" % w.getpeername()[0], "queue is empty..")
outputs.remove(w)
# 隊列不為空,就把隊列中的數(shù)據(jù)改成大寫,原樣發(fā)回去
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
服務(wù)端:
import selectors
import socket
# 根據(jù)平臺自動選擇最佳的IO多路機(jī)制,比如linux就會選擇epoll,windows會選擇select
sel = selectors.DefaultSelector()
def accept(sock, mask):
# 建立客戶端連接
conn, addr = sock.accept()
print("accepted", conn, "from", addr)
# 設(shè)置非阻塞模式
conn.setblocking(False)
# 再次注冊一個連接,將其加入監(jiān)測列表中,
sel.register(conn, selectors.EVENT_READ, read)
def read(conn, mask):
try: # 拋出客戶端強(qiáng)制關(guān)閉的異常(如手動關(guān)閉客戶端黑窗口)
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從監(jiān)測列表刪除
sel.unregister(conn)
conn.close()
except ConnectionResetError:
print("Client forcibly closed.", conn)
# 將conn從監(jiān)測列表刪除
sel.unregister(conn)
conn.close()
# 創(chuàng)建socket對象
sock = socket.socket()
# 綁定端口,設(shè)置監(jiān)聽
sock.bind(("localhost", 1234))
sock.listen(100)
# 設(shè)置為非阻塞模式
sock.setblocking(False)
# 注冊一個文件對象,監(jiān)測它的IO事件,data是和文件對象相關(guān)的數(shù)據(jù)(此處放置了一個 accept 函數(shù)的內(nèi)存地址)
# register(fileobj, events, data=None)
sel.register(sock, selectors.EVENT_READ, accept)
while True:
"""
sel.select()
看似是select方法,實(shí)際上會根據(jù)平臺自動選擇使用select還是epoll
它返回一個(key, events)元組, key是一個namedtuple類型的元組,可以使用 key.name 獲取元組的數(shù)據(jù)
key 的內(nèi)容(fileobj,fd,events,data):
fileobj 已經(jīng)注冊的文件對象
fd 也就是第一個參數(shù)的那個文件對象的更底層的文件描述符
events 等待的IO事件
data 可選項。可以存一些和fileobj有關(guān)的數(shù)據(jù),如 sessioin 的 id
"""
events = sel.select() # 監(jiān)測有無活動對象,沒有就阻塞在這里等待
for key, mask in events: # 有活動對象了
callback = key.data # key.data 是注冊時傳遞的 accept 函數(shù)
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"))
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持云海天教程。
原文鏈接:https://www.cnblogs.com/wztshine/p/12091062.html
總結(jié)
以上是生活随笔為你收集整理的python中select用法_Python select及selectors模块概念用法详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么用引导盘装系统 使用引导盘安装系统的
- 下一篇: diskgenius怎么设置主分区 di