python模塊介紹- select 等待I/0完成

select同時監控多個sockets,支持網絡服務和多個客戶端通訊。html

如下內容部分來源於:http://www.cnblogs.com/coser/archive/2012/01/06/2315216.htmlpython

該模塊能夠訪問大多數操做系統中的select()和poll()函數, Linux2.5+支持的epoll()和大多數BSD支持的kqueue()。請注意,在Windows上,它僅適用於select,在其餘操做系統上,它也適用於其餘類型的文件(特別是在Unix上,它還能夠用於管道)。它不能用於肯定常規文件是否變化。shell

Select:synchronous I/O multiplexing. select最先於1983年出如今4.2BSD中,它經過一個select()系統調用來監視多個文件描述符的數組,當select()返回後,該數組中就緒的文件描述符便會被內核修改標誌位,使得進程能夠得到這些文件描述符從而進行後續的讀寫操做。Select是跨平臺的。select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,不過能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制。select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,不過能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制。windows

Poll:wait for some event on a file descriptor。poll在1986年誕生於System V Release 3,它和select在本質上沒有多大差異,可是poll沒有最大文件描述符數量的限制。poll和select一樣存在一個缺點就是,包含大量文件描述符的數組被總體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增長而線性增大。另外,select()和poll()將就緒的文件描述符告訴進程後,若是進程沒有對其進行IO操做,那麼下次調用select()和poll()的時候 將再次報告這些文件描述符,因此它們通常不會丟失就緒的消息,這種方式稱爲水平觸發(Level Triggered)。不過poll並不適用於windows平臺。數組

Epoll:I/O event notification facility。Linux 2.5.44之後支持,是性能最好的多路I/O就緒通知方法。epoll能夠同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變爲就緒狀態,它只說一遍,若是咱們沒有采起行動,那麼它將不會再次告知,這種方式稱爲邊緣觸發),理論上邊緣觸發的性能要更高一些,可是代碼實現至關複雜。epoll一樣只告知那些就緒的文件描述符,並且當咱們調用epoll_wait()得到就緒文件描述符時,返回的不是實際的描述符,而是一個表明就緒描 述符數量的值,你只須要去epoll指定的一個數組中依次取得相應數量的文件描述符便可,這裏也使用了內存映射(mmap)技術,這樣便完全省掉了這些文件描述符在系統調用時複製的開銷。另外一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用必定的方法後,內核纔對全部監視的文件描述符進 行掃描,而epoll事先經過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用相似callback的回調機制, 迅速激活這個文件描述符,當進程調用epoll_wait()時便獲得通知。服務器

Kqueue:暫不涉及。網絡

使用select

select監控sockets,打開的文件,管道(任何有fileno()方法返回一個有效的文件描述符的東東),直到它們變成可讀可寫或發生通信錯誤。它能夠更容易地同時監控多個鏈接,比使用socket超時輪詢更有效,由於監控在操做系統網絡層而不是python解析器。注意windows的select並不支持打開文件。併發

下面代碼經過select監控多個鏈接。注意經過server.setblocking(0)設置爲非阻塞模式。select()的參數爲3個列表:第一列表爲讀取輸入數據的對象;第2個接收要發送的數據,第3個存放errors。select()返回的3個列表和輸入相似:readable,writable,exceptional。app

readable有3種可能:對於用來偵聽鏈接主服務器socket,表示已準備好接受一個到來的鏈接;對於已經創建併發送數據的連接,表示有數據到來;若是沒數據到來,表示連接已經關閉。socket

writable的狀況:鏈接隊列中有數據,發送下一條消息。若是隊列中無數據,則從output隊列中刪除。

socket有錯誤,也要從output隊列中刪除。

select_echo_server.py

import selectimport socketimport sysimport Queue# Create a TCP/IP socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.setblocking(0)# Bind the socket to the portserver_address = ('localhost', 10000)print >>sys.stderr, 'starting up on %s port %s' % server_address
server.bind(server_address)# Listen for incoming connectionsserver.listen(5)# Sockets from which we expect to readinputs = [ server ]# Sockets to which we expect to writeoutputs = [ ]# Outgoing message queues (socket:Queue)message_queues = {}while inputs:

    # Wait for at least one of the sockets to be ready for processing
    print >>sys.stderr, 'waiting for the next event'
    readable, writable, exceptional = select.select(inputs, outputs, inputs)

    # Handle inputs
    for s in readable:

        if s is server:
            # A "readable" socket is ready to accept a connection
            connection, client_address = s.accept()
            print >>sys.stderr, '  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()

        else:
            data = s.recv(1024)
            if data:
                # A readable client socket has data
                print >>sys.stderr, '  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)
                    
            else:
                # Interpret empty result as closed connection
                print >>sys.stderr, '  closing', client_address                # Stop listening for input on the connection
                if s in outputs:
                    outputs.remove(s)
                inputs.remove(s)
                s.close()

                # Remove message queue
                del message_queues[s]

    # 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 >>sys.stderr, '  ', s.getpeername(), 'queue empty'
            outputs.remove(s)
        else:
            print >>sys.stderr, '  sending "%s" to %s' % \                (next_msg, s.getpeername())
            s.send(next_msg)

    # Handle "exceptional conditions"
    for s in exceptional:
        print >>sys.stderr, 'exception condition on', 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_echo_multiclient.py

import socketimport sysmessages = [ 'This is the message. ',
             'It will be sent ',
             'in parts.',
             ]server_address = ('localhost', 10000)# Create a TCP/IP socketsocks = [ 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 listeningprint >>sys.stderr, 'connecting to %s port %s' % server_addressfor s in socks:
    s.connect(server_address)for message in messages:

    # Send messages on both sockets
    for s in socks:
        print >>sys.stderr, '%s: sending "%s"' % \            (s.getsockname(), message)
        s.send(message)

    # Read responses on both sockets
    for s in socks:
        data = s.recv(1024)
        print >>sys.stderr, '%s: received "%s"' % \            (s.getsockname(), data)
        if not data:
            print >>sys.stderr, 'closing socket', s.getsockname()
            s.close()

執行結果:

# ./select_echo_server.py 
starting up on localhost port 10000
waiting for the next event
  connection from ('127.0.0.1', 35424)waiting for the next event
  connection from ('127.0.0.1', 35425)waiting for the next event
  received "This is the message. " from ('127.0.0.1', 35424)
  received "This is the message. " from ('127.0.0.1', 35425)waiting for the next event
  sending "This is the message. " to ('127.0.0.1', 35424)
  sending "This is the message. " to ('127.0.0.1', 35425)waiting for the next event   ('127.0.0.1', 35424) queue empty   ('127.0.0.1', 35425) queue empty
waiting for the next event
  received "It will be sent " from ('127.0.0.1', 35424)
  received "It will be sent " from ('127.0.0.1', 35425)waiting for the next event
  sending "It will be sent " to ('127.0.0.1', 35424)
  sending "It will be sent " to ('127.0.0.1', 35425)waiting for the next event   ('127.0.0.1', 35424) queue empty   ('127.0.0.1', 35425) queue empty
waiting for the next event
  received "in parts." from ('127.0.0.1', 35424)
  received "in parts." from ('127.0.0.1', 35425)waiting for the next event
  sending "in parts." to ('127.0.0.1', 35424)
  sending "in parts." to ('127.0.0.1', 35425)waiting for the next event   ('127.0.0.1', 35424) queue empty   ('127.0.0.1', 35425) queue empty
waiting for the next event
  closing ('127.0.0.1', 35425)waiting for the next event
  closing ('127.0.0.1', 35425)waiting for the next event
# ./select_echo_multiclient.py 
connecting to localhost port 10000('127.0.0.1', 35424): sending "This is the message. "('127.0.0.1', 35425): sending "This is the message. "('127.0.0.1', 35424): received "This is the message. "('127.0.0.1', 35425): received "This is the message. "('127.0.0.1', 35424): sending "It will be sent "('127.0.0.1', 35425): sending "It will be sent "('127.0.0.1', 35424): received "It will be sent "('127.0.0.1', 35425): received "It will be sent "('127.0.0.1', 35424): sending "in parts."('127.0.0.1', 35425): sending "in parts."('127.0.0.1', 35424): received "in parts."('127.0.0.1', 35425): received "in parts."

非阻塞I/0中使用超時

Select的第四個參數能夠設置超時。超時時,select()返回3個空列表。

服務器端修改以下:

    print >>sys.stderr, '\nwaiting for the next event'
    timeout = 1
    readable, writable, exceptional = select.select(
        inputs, outputs, inputs, timeout)

    if not (readable or writable or exceptional):
        print >>sys.stderr, '  timed out, do some other work here'
        continue

客戶端:

import socketimport sysimport time# Create a TCP/IP socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Connect the socket to the port where the server is listeningserver_address = ('localhost', 10000)print >>sys.stderr, 'connecting to %s port %s' % server_address
sock.connect(server_address)time.sleep(1)messages = [ 'Part one of the message.',
             'Part two of the message.',
             ]amount_expected = len(''.join(messages))try:

    # Send data
    for message in messages:
        print >>sys.stderr, 'sending "%s"' % message
        sock.sendall(message)
        time.sleep(1.5)

    # Look for the response
    amount_received = 0
    
    while amount_received < amount_expected:
        data = sock.recv(16)
        amount_received += len(data)
        print >>sys.stderr, 'received "%s"' % datafinally:
    print >>sys.stderr, 'closing socket'
    sock.close()

執行結果:

# python select_echo_server_timeout.pywaiting for the next event
  timed out, do some other work here

waiting for the next event
connection from ('127.0.0.1', 52875)waiting for the next event
received "Part one of the message." from ('127.0.0.1', 52875)waiting for the next event
sending "Part one of the message." to ('127.0.0.1', 52875)waiting for the next event('127.0.0.1', 52875) queue empty

waiting for the next event
  timed out, do some other work here

waiting for the next event
received "Part two of the message." from ('127.0.0.1', 52875)waiting for the next event
sending "Part two of the message." to ('127.0.0.1', 52875)waiting for the next event('127.0.0.1', 52875) queue empty

waiting for the next event
  timed out, do some other work here

waiting for the next event
closing ('127.0.0.1', 52875)waiting for the next event
  timed out, do some other work here
# python select_echo_slow_client.py 
connecting to localhost port 10000
sending "Part one of the message."sending "Part two of the message."received "Part one of the "received "message.Part two"received " of the message."closing socket

poll

Poll和select相似,但底層實現更有效, 但不支持windows。它的timeout是毫秒爲單位的,而select是秒。

Python的有類管理監控註冊數據通道。經過register()添加數據通道,標誌表示要關注哪些事件。標誌flag列表以下:

  • POLLIN:Input ready

  • POLLPRI:Priority input ready

  • POLLOUT:Able to receive output

  • POLLERR:Error

  • POLLHUP:Channel closed

  • POLLNVAL:Channel not open

下面的實例參見《The Python Standard Library by Example 2011》

其餘

Epoll是poll的擴展,Kqueue是BSD內核隊列,kevent是BSD內核event。

最新版本參見


參考資料

本文地址

相關文章
相關標籤/搜索