python select模块_Python之select模块解析
首先列一下,sellect、poll、epoll三者的區別
select
select最早于1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組,當select()返回后,該數組中就緒的文件描述符便會被內核修改標志位,使得進程可以獲得這些文件描述符從而進行后續的讀寫操作。
select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。
select的一個缺點在于單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。
另外,select()所維護的存儲大量文件描述符的數據結構,隨著文件描述符數量的增大,其復制的開銷也線性增長。同時,由于網絡響應時間的延遲使得大量TCP連接處于非活躍狀態,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。
poll
poll在1986年誕生于System V Release 3,它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制。
poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制于用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數量的增加而線性增大。
另外,select()和poll()將就緒的文件描述符告訴進程后,如果進程沒有對其進行IO操作,那么下次調用select()和poll()的時候將再次報告這些文件描述符,所以它們一般不會丟失就緒的消息,這種方式稱為水平觸發(Level Triggered)。
epoll
直到Linux2.6才出現了由內核直接支持的實現方法,那就是epoll,它幾乎具備了之前所說的一切優點,被公認為Linux2.6下性能最好的多路I/O就緒通知方法。
epoll可以同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有采取行動,那么它將不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當復雜。
epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這里也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時復制的開銷。
另一個本質的改進在于epoll采用基于事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。
Python select
Python的select()方法直接調用操作系統的IO接口,它監控sockets,open files, and pipes(所有帶fileno()方法的文件句柄)何時變成readable 和writeable, 或者通信錯誤,select()使得同時監控多個連接變的簡單,并且這比寫一個長循環來等待和監控多客戶端連接要高效,因為select直接通過操作系統提供的C的網絡接口進行操作,而不是通過Python的解釋器。
注意:Using Python’s file objects with?select()?works for Unix, but is not supported under Windows.
接下來通過echo server例子要以了解select 是如何通過單進程實現同時處理多個非阻塞的socket連接的。
import select
import socket
import sys
import queue
# Create a TCP/IP socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0)
# Bind the socket to the port
server_address = ('localhost', 10000)
print('starting up on %s port %s' % server_address)
server.bind(server_address)
# Listen for incoming connections
server.listen(5)
select()方法接收并監控3個通信列表, 第一個是所有的輸入的data,就是指外部發過來的數據,第2個是監控和接收所有要發出去的data(outgoing data),第3個監控錯誤信息,接下來我們需要創建2個列表來包含輸入和輸出信息來傳給select().
# Sockets from which we expect to read
inputs = [ server ]
# Sockets to which we expect to write
outputs = [ ]
所有客戶端的進來的連接和數據將會被server的主循環程序放在上面的list中處理,我們現在的server端需要等待連接可寫(writable)之后才能過來,然后接收數據并返回(因此不是在接收到數據之后就立刻返回),因為每個連接要把輸入或輸出的數據先緩存到queue里,然后再由select取出來再發出去。
Connections are added to and removed from these lists by the server main loop. Since this version of the server is going to wait for a socket to become writable before sending any data (instead of immediately sending the reply), each output connection needs a queue to act as a buffer for the data to be sent through it.
# Outgoing message queues (socket:queue)
message_queues = {}
The main portion of the server program loops, calling?select()?to block and wait for network activity.
下面是此程序的主循環,調用select()時會阻塞和等待直到新的連接和數據進來
while inputs:
# Wait for at least one of the sockets to be ready for processing
print('waiting for the next event')
readable, writable, exceptional = select.select(inputs, outputs, inputs)
當你把inputs,outputs,exceptional(這里跟inputs共用)傳給
select()?returns three new lists, containing subsets of the contents of the lists passed in. All of the sockets in the?readable?list have incoming data buffered and available to be read. All of the sockets in the?writable?list have free space in their buffer and can be written to. The sockets returned in?exceptional?have had an error (the actual definition of “exceptional condition” depends on the platform).
Readable list 中的socket 可以有3種可能狀態,第一種是如果這個socket是main "server" socket,它負責監聽客戶端的連接,如果這個main server socket出現在readable里,那代表這是server端已經ready來接收一個新的連接進來了,為了讓這個main server能同時處理多個連接,在下面的代碼里,我們把這個main server的socket設置為非阻塞模式。
The “readable” sockets represent three possible cases. If the socket is the main “server” socket, the one being used to listen for connections, then the “readable” condition means it is ready to accept another incoming connection. In addition to adding the new connection to the list of inputs to monitor, this section sets the client socket to not block.
# Handle inputs
for s in readable:
if s is server:
# A "readable" server socket is ready to accept a connection
connection, client_address = s.accept()
print('new connection from', client_address)
connection.setblocking(0)
inputs.append(connection)
# Give the connection a queue for data we want to send
message_queues[connection] = queue.queue()
第二種情況是這個socket是已經建立了的連接,它把數據發了過來,這個時候你就可以通過recv()來接收它發過來的數據,然后把接收到的數據放到queue里,這樣你就可以把接收到的數據再傳回給客戶端了。
The next case is an established connection with a client that has sent data. The data is read with?recv(), then placed on the queue so it can be sent through the socket and back to the client.
else:
data = s.recv(1024)
if data:
# A readable client socket has data
print('received "%s" from %s' % (data, s.getpeername()))
message_queues[s].put(data)
# Add output channel for response
if s not in outputs:
outputs.append(s)
第三種情況就是這個客戶端已經斷開了,所以你再通過recv()接收到的數據就為空了,所以這個時候你就可以把這個跟客戶端的連接關閉了。
A readable socket?without?data available is from a client that has disconnected, and the stream is ready to be closed.
else:
# Interpret empty result as closed connection
print('closing', client_address, 'after reading no data')
# Stop listening for input on the connection
if s in outputs:
outputs.remove(s) #既然客戶端都斷開了,我就不用再給它返回數據了,所以這時候如果這個客戶端的連接對象還在outputs列表中,就把它刪掉
inputs.remove(s) #inputs中也刪除掉
s.close() #把這個連接關閉掉
# Remove message queue
del message_queues[s]
對于writable list中的socket,也有幾種狀態,如果這個客戶端連接在跟它對應的queue里有數據,就把這個數據取出來再發回給這個客戶端,否則就把這個連接從output list中移除,這樣下一次循環select()調用時檢測到outputs list中沒有這個連接,那就會認為這個連接還處于非活動狀態
There are fewer cases for the writable connections. If there is data in the queue for a connection, the next message is sent. Otherwise, the connection is removed from the list of output connections so that the next time through the loop?select()?does not indicate that the socket is ready to send data.
# Handle outputs
for s in writable:
try:
next_msg = message_queues[s].get_nowait()
except queue.Empty:
# No messages waiting so stop checking for writability.
print('output queue for', s.getpeername(), 'is empty')
outputs.remove(s)
else:
print('sending "%s" to %s' % (next_msg, s.getpeername()))
s.send(next_msg)
最后,如果在跟某個socket連接通信過程中出了錯誤,就把這個連接對象在inputs\outputs\message_queue中都刪除,再把連接關閉掉
# Handle "exceptional conditions"
for s in exceptional:
print('handling exceptional condition for', s.getpeername())
# Stop listening for input on the connection
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
# Remove message queue
del message_queues[s]
客戶端
下面的這個是客戶端程序展示了如何通過select()對socket進行管理并與多個連接同時進行交互,
The example client program uses two sockets to demonstrate how the server with?select()?manages multiple connections at the same time. The client starts by connecting each TCP/IP socket to the server.
import socket
import sys
messages = [ 'This is the message. ',
'It will be sent ',
'in parts.',
]
server_address = ('localhost', 10000)
# Create a TCP/IP socket
socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM),
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
]
# Connect the socket to the port where the server is listening
prin('connecting to %s port %s' % server_address)
for s in socks:
s.connect(server_address)
接下來通過循環通過每個socket連接給server發送和接收數據。
Then it sends one pieces of the message at a time via each socket, and reads all responses available after writing new data.
for message in messages:
# Send messages on both sockets
for s in socks:
print('%s: sending "%s"' % (s.getsockname(), message))
s.send(message)
# Read responses on both sockets
for s in socks:
data = s.recv(1024)
print( '%s: received "%s"' % (s.getsockname(), data))
if not data:
print('closing socket', s.getsockname())
最后服務器端的完整代碼如下:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Version:Python3.5.0
import select
import socket
import sys
import queue
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建一個TCP/IP socket
server.setblocking(False) # 設置非阻塞狀態
server_address = ('localhost', 9999) # 設置服務器IP以及端口號
print('啟動 [%s] 端口號 [%s]' % server_address)
server.bind(server_address) # 綁定IP以及端口號
server.listen(5) # 設置監聽5個連接
inputs = [server, ] # 保存連接過來的連接到一個列表中
outputs = [] # 保存用來寫操作的socket
message_queues = {} # 設置存放連接隊列的字典
while inputs:
# 等待至少一個socket來處理
print('在等待下一個事件')
readable, writable, exceptional = select.select(inputs, outputs, inputs)
# 輸入句柄
for s in readable:
if s is server: # 一個新的連接
# 一個可讀的server socket 準備接收一個連接
connection, client_address = s.accept()
print('新連接來自:', client_address)
connection.setblocking(False) # 設置非阻塞
inputs.append(connection) # 把新連接添加入連接列表中
# 把這個連接添加入連接隊列字典中
message_queues[connection] = queue.Queue()
else: # 有數據的連接
data = s.recv(1024) # 接收最大為1024字節的數據
if data: # 一個可讀的客戶端socket有數據
print('接收 "%s" 來自 %s' % (data, s.getpeername()) )
message_queues[s].put(data) # 放進一個隊列里面
# Add output channel for response
if s not in outputs:
outputs.append(s) # 把s添加到output列表中去
else:
# 沒有數據則關閉連接
print('沒有數據過來,關閉:', client_address)
# 停止監聽斷開的連接
if s in outputs:
outputs.remove(s) #既然客戶端都斷開了,我就不用再給它返回數據了,所以這時候如果這個客戶端的連接對象還在outputs列表中,就把它刪掉
inputs.remove(s) #inputs中也刪除掉
s.close() #把這個連接關閉掉
del message_queues[s] # 刪除連接隊列字典的數據
# 輸出句柄
for s in writable:
try:
next_msg = message_queues[s].get_nowait() # 讀取數據
except queue.Empty:
# No messages waiting so stop checking for writability.
print('output隊列',s.getpeername(),'是空的')
outputs.remove(s)
else:
print('發送 "%s" 到 %s' % (next_msg, s.getpeername()))
s.send(next_msg) # 取到數據后發給客戶端
# Handle "exceptional conditions"
for s in exceptional:
print('handling exceptional condition for', s.getpeername() )
inputs.remove(s) # 停止監控input 連接
if s in outputs:
outputs.remove(s)
s.close()
del message_queues[s] # 刪除連接隊列
客戶端的完整代碼如下:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Version:Python3.5.0
import socket
import sys
messages = ['This is the message.',
'It will be sent.',
'in parts.']
server_address = ('localhost', 9999) # 設置服務器IP以及端口號
# 創建TCP/IP socket 列表,總共50個連接
socks =[socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(50)]
print('連接到服務器:%s 端口號:%s' % server_address)
for s in socks:
s.connect(server_address) # 連接socks列表中的所有socket
for msg in messages:
# 所有socket發送信息到服務器
for s in socks:
print(s.getsockname(), ': 發送數據:"%s"' % msg)
s.send(bytes(msg, 'utf8'))
# 接收服務器返回的信息
for s in socks:
data = s.recv(1024) # 設置接收大小為1024字節
print('%s: 接收數據:"%s"' % (s.getsockname(), data.decode()))
if not data: # 沒有接收到數據,說明接收完畢
print('關閉socket', s.getsockname())
s.close() # 關閉socket
執行結果如下
服務器:
啟動 [localhost] 端口號 [9999]
在等待下一個事件
新連接來自: ('127.0.0.1', 60680)
在等待下一個事件
新連接來自: ('127.0.0.1', 60681)
在等待下一個事件
新連接來自: ('127.0.0.1', 60682)
在等待下一個事件
新連接來自: ('127.0.0.1', 60683)
在等待下一個事件
新連接來自: ('127.0.0.1', 60684)
在等待下一個事件
接收 "b'This is the message.'" 來自 ('127.0.0.1', 60680)
接收 "b'This is the message.'" 來自 ('127.0.0.1', 60681)
在等待下一個事件
接收 "b'This is the message.'" 來自 ('127.0.0.1', 60682)
接收 "b'This is the message.'" 來自 ('127.0.0.1', 60683)
接收 "b'This is the message.'" 來自 ('127.0.0.1', 60684)
發送 "b'This is the message.'" 到 ('127.0.0.1', 60680)
發送 "b'This is the message.'" 到 ('127.0.0.1', 60681)
在等待下一個事件
output隊列 ('127.0.0.1', 60680) 是空的
output隊列 ('127.0.0.1', 60681) 是空的
發送 "b'This is the message.'" 到 ('127.0.0.1', 60682)
發送 "b'This is the message.'" 到 ('127.0.0.1', 60683)
發送 "b'This is the message.'" 到 ('127.0.0.1', 60684)
在等待下一個事件
output隊列 ('127.0.0.1', 60682) 是空的
output隊列 ('127.0.0.1', 60683) 是空的
output隊列 ('127.0.0.1', 60684) 是空的
在等待下一個事件
接收 "b'It will be sent.'" 來自 ('127.0.0.1', 60680)
接收 "b'It will be sent.'" 來自 ('127.0.0.1', 60681)
在等待下一個事件
接收 "b'It will be sent.'" 來自 ('127.0.0.1', 60682)
接收 "b'It will be sent.'" 來自 ('127.0.0.1', 60683)
接收 "b'It will be sent.'" 來自 ('127.0.0.1', 60684)
發送 "b'It will be sent.'" 到 ('127.0.0.1', 60680)
發送 "b'It will be sent.'" 到 ('127.0.0.1', 60681)
在等待下一個事件
output隊列 ('127.0.0.1', 60680) 是空的
output隊列 ('127.0.0.1', 60681) 是空的
發送 "b'It will be sent.'" 到 ('127.0.0.1', 60682)
發送 "b'It will be sent.'" 到 ('127.0.0.1', 60683)
發送 "b'It will be sent.'" 到 ('127.0.0.1', 60684)
在等待下一個事件
output隊列 ('127.0.0.1', 60682) 是空的
output隊列 ('127.0.0.1', 60683) 是空的
output隊列 ('127.0.0.1', 60684) 是空的
在等待下一個事件
接收 "b'in parts.'" 來自 ('127.0.0.1', 60680)
在等待下一個事件
接收 "b'in parts.'" 來自 ('127.0.0.1', 60681)
發送 "b'in parts.'" 到 ('127.0.0.1', 60680)
在等待下一個事件
接收 "b'in parts.'" 來自 ('127.0.0.1', 60682)
接收 "b'in parts.'" 來自 ('127.0.0.1', 60683)
接收 "b'in parts.'" 來自 ('127.0.0.1', 60684)
output隊列 ('127.0.0.1', 60680) 是空的
發送 "b'in parts.'" 到 ('127.0.0.1', 60681)
在等待下一個事件
output隊列 ('127.0.0.1', 60681) 是空的
發送 "b'in parts.'" 到 ('127.0.0.1', 60682)
發送 "b'in parts.'" 到 ('127.0.0.1', 60683)
發送 "b'in parts.'" 到 ('127.0.0.1', 60684)
在等待下一個事件
output隊列 ('127.0.0.1', 60682) 是空的
output隊列 ('127.0.0.1', 60683) 是空的
output隊列 ('127.0.0.1', 60684) 是空的
在等待下一個事件
沒有數據過來,關閉: ('127.0.0.1', 60684)
在等待下一個事件
沒有數據過來,關閉: ('127.0.0.1', 60684)
在等待下一個事件
沒有數據過來,關閉: ('127.0.0.1', 60684)
沒有數據過來,關閉: ('127.0.0.1', 60684)
在等待下一個事件
沒有數據過來,關閉: ('127.0.0.1', 60684)
在等待下一個事件
客戶端:
連接到服務器:localhost 端口號:9999
('127.0.0.1', 60680) : 發送數據:"This is the message."
('127.0.0.1', 60681) : 發送數據:"This is the message."
('127.0.0.1', 60682) : 發送數據:"This is the message."
('127.0.0.1', 60683) : 發送數據:"This is the message."
('127.0.0.1', 60684) : 發送數據:"This is the message."
('127.0.0.1', 60680): 接收數據:"This is the message."
('127.0.0.1', 60681): 接收數據:"This is the message."
('127.0.0.1', 60682): 接收數據:"This is the message."
('127.0.0.1', 60683): 接收數據:"This is the message."
('127.0.0.1', 60684): 接收數據:"This is the message."
('127.0.0.1', 60680) : 發送數據:"It will be sent."
('127.0.0.1', 60681) : 發送數據:"It will be sent."
('127.0.0.1', 60682) : 發送數據:"It will be sent."
('127.0.0.1', 60683) : 發送數據:"It will be sent."
('127.0.0.1', 60684) : 發送數據:"It will be sent."
('127.0.0.1', 60680): 接收數據:"It will be sent."
('127.0.0.1', 60681): 接收數據:"It will be sent."
('127.0.0.1', 60682): 接收數據:"It will be sent."
('127.0.0.1', 60683): 接收數據:"It will be sent."
('127.0.0.1', 60684): 接收數據:"It will be sent."
('127.0.0.1', 60680) : 發送數據:"in parts."
('127.0.0.1', 60681) : 發送數據:"in parts."
('127.0.0.1', 60682) : 發送數據:"in parts."
('127.0.0.1', 60683) : 發送數據:"in parts."
('127.0.0.1', 60684) : 發送數據:"in parts."
('127.0.0.1', 60680): 接收數據:"in parts."
('127.0.0.1', 60681): 接收數據:"in parts."
('127.0.0.1', 60682): 接收數據:"in parts."
('127.0.0.1', 60683): 接收數據:"in parts."
('127.0.0.1', 60684): 接收數據:"in parts."
總結
以上是生活随笔為你收集整理的python select模块_Python之select模块解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python定义方法self会被当作变量
- 下一篇: python 搭建web应用程序_用Py