python走起之第九話

協程

協程,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程html

協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。所以:python

協程能保留上一次調用時的狀態(即全部局部狀態的一個特定組合),每次過程重入時,就至關於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。程序員

 

協程的好處:編程

  • 無需線程上下文切換的開銷
  • 無需原子操做鎖定及同步的開銷
  • 方便切換控制流,簡化編程模型
  • 高併發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。因此很適合用於高併發處理。

缺點:數組

  • 沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上.固然咱們平常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  • 進行阻塞(Blocking)操做(如IO時)會阻塞掉整個程序

使用yield實現協程操做例子    緩存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import  time
import  queue
def  consumer(name):
     print ( "--->starting eating baozi..." )
     while  True :
         new_baozi  =  yield
         print ( "[%s] is eating baozi %s"  %  (name,new_baozi))
         #time.sleep(1)
 
def  producer():
 
     =  con.__next__()
     =  con2.__next__()
     =  0
     while  n <  5 :
         + = 1
         con.send(n)
         con2.send(n)
         print ( "\033[32;1m[producer]\033[0m is making baozi %s"  % n )
 
 
if  __name__  = =  '__main__' :
     con  =  consumer( "c1" )
     con2  =  consumer( "c2" )
     =  producer()

Greenlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
  
from  greenlet  import  greenlet
  
  
def  test1():
     print  12
     gr2.switch()
     print  34
     gr2.switch()
  
  
def  test2():
     print  56
     gr1.switch()
     print  78
  
gr1  =  greenlet(test1)
gr2  =  greenlet(test2)
gr1.switch()

  

Gevent 

Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import  gevent
 
def  foo():
     print ( 'Running in foo' )
     gevent.sleep( 0 )
     print ( 'Explicit context switch to foo again' )
 
def  bar():
     print ( 'Explicit context to bar' )
     gevent.sleep( 0 )
     print ( 'Implicit context switch back to bar' )
 
gevent.joinall([
     gevent.spawn(foo),
     gevent.spawn(bar),
])

輸出:服務器

Running in foo Explicit context to bar Explicit context switch to foo again Implicit context switch back to bar 

同步與異步的性能區別 網絡

上面程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn。 初始化的greenlet列表存放在數組threads中,此數組被傳給gevent.joinall 函數,後者阻塞當前流程,並執行全部給定的greenlet。執行流程只會在 全部greenlet執行完後纔會繼續向下走。  數據結構

遇到IO阻塞時會自動切換任務

 

經過gevent實現單線程下的多socket併發

server side 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import  sys
import  socket
import  time
import  gevent
 
from  gevent  import  socket,monkey
monkey.patch_all()
 
 
def  server(port):
     =  socket.socket()
     s.bind(( '0.0.0.0' , port))
     s.listen( 500 )
     while  True :
         cli, addr  =  s.accept()
         gevent.spawn(handle_request, cli)
 
 
 
def  handle_request(conn):
     try :
         while  True :
             data  =  conn.recv( 1024 )
             print ( "recv:" , data)
             conn.send(data)
             if  not  data:
                 conn.shutdown(socket.SHUT_WR)
 
     except  Exception as  ex:
         print (ex)
     finally :
         conn.close()
if  __name__  = =  '__main__' :
     server( 8001 )
 
 

 

論事件驅動與異步IO

一般,咱們寫服務器處理模型的程序時,有如下幾種模型:
(1)每收到一個請求,建立一個新的進程,來處理該請求;
(2)每收到一個請求,建立一個新的線程,來處理該請求;
(3)每收到一個請求,放入一個事件列表,讓主進程經過非阻塞I/O方式來處理請求
上面的幾種方式,各有千秋,
第(1)中方法,因爲建立新的進程的開銷比較大,因此,會致使服務器性能比較差,但實現比較簡單。
第(2)種方式,因爲要涉及到線程的同步,有可能會面臨 死鎖等問題。
第(3)種方式,在寫應用程序代碼時,邏輯比前面兩種都複雜。
綜合考慮各方面因素,通常廣泛認爲第(3)種方式是大多數 網絡服務器採用的方式
 

看圖說話講事件驅動模型

在UI編程中,經常要對鼠標點擊進行相應,首先如何得到鼠標點擊呢?
方式一:建立一個線程,該線程一直循環檢測是否有鼠標點擊,那麼這個方式有如下幾個缺點
1. CPU資源浪費,可能鼠標點擊的頻率很是小,可是掃描線程仍是會一直循環檢測,這會形成不少的CPU資源浪費;若是掃描鼠標點擊的接口是阻塞的呢?
2. 若是是堵塞的,又會出現下面這樣的問題,若是咱們不但要掃描鼠標點擊,還要掃描鍵盤是否按下,因爲掃描鼠標時被堵塞了,那麼可能永遠不會去掃描鍵盤;
3. 若是一個循環須要掃描的設備很是多,這又會引來響應時間的問題;
因此,該方式是很是很差的。

方式二:就是事件驅動模型
目前大部分的UI編程都是事件驅動模型,如不少UI平臺都會提供onClick()事件,這個事件就表明鼠標按下事件。事件驅動模型大致思路以下:
1. 有一個事件(消息)隊列;
2. 鼠標按下時,往這個隊列中增長一個點擊事件(消息);
3. 有個循環,不斷從隊列取出事件,根據不一樣的事件,調用不一樣的函數,如onClick()、onKeyDown()等;
4. 事件(消息)通常都各自保存各自的處理函數指針,這樣,每一個消息都有獨立的處理函數;

 

 

 

事件驅動編程是一種編程範式,這裏程序的執行流由外部事件來決定。它的特色是包含一個事件循環,當外部事件發生時使用回調機制來觸發相應的處理。另外兩種常見的編程範式是(單線程)同步以及多線程編程。

讓咱們用例子來比較和對比一下單線程、多線程以及事件驅動編程模型。下圖展現了隨着時間的推移,這三種模式下程序所作的工做。這個程序有3個任務須要完成,每一個任務都在等待I/O操做時阻塞自身。阻塞在I/O操做上所花費的時間已經用灰色框標示出來了。

 

在單線程同步模型中,任務按照順序執行。若是某個任務由於I/O而阻塞,其餘全部的任務都必須等待,直到它完成以後它們才能依次執行。這種明確的執行順序和串行化處理的行爲是很容易推斷得出的。若是任務之間並無互相依賴的關係,但仍然須要互相等待的話這就使得程序沒必要要的下降了運行速度。

在多線程版本中,這3個任務分別在獨立的線程中執行。這些線程由操做系統來管理,在多處理器系統上能夠並行處理,或者在單處理器系統上交錯執行。這使得當某個線程阻塞在某個資源的同時其餘線程得以繼續執行。與完成相似功能的同步程序相比,這種方式更有效率,但程序員必須寫代碼來保護共享資源,防止其被多個線程同時訪問。多線程程序更加難以推斷,由於這類程序不得不經過線程同步機制如鎖、可重入函數、線程局部存儲或者其餘機制來處理線程安全問題,若是實現不當就會致使出現微妙且使人痛不欲生的bug。

在事件驅動版本的程序中,3個任務交錯執行,但仍然在一個單獨的線程控制中。當處理I/O或者其餘昂貴的操做時,註冊一個回調到事件循環中,而後當I/O操做完成時繼續執行。回調描述了該如何處理某個事件。事件循環輪詢全部的事件,當事件到來時將它們分配給等待處理事件的回調函數。這種方式讓程序儘量的得以執行而不須要用到額外的線程。事件驅動型程序比多線程程序更容易推斷出行爲,由於程序員不須要關心線程安全問題。

當咱們面對以下的環境時,事件驅動模型一般是一個好的選擇:

  1. 程序中有許多任務,並且…
  2. 任務之間高度獨立(所以它們不須要互相通訊,或者等待彼此)並且…
  3. 在等待事件到來時,某些任務會阻塞。

當應用程序須要在任務間共享可變的數據時,這也是一個不錯的選擇,由於這裏不須要採用同步處理。

網絡應用程序一般都有上述這些特色,這使得它們可以很好的契合事件驅動編程模型。

Select\Poll\Epoll異步IO

首先列一下,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鏈接的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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  >>sys.stderr,  '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 >>sys.stderr, '\nwaiting for the next event' readable, writable, exceptional = select.select(inputs, outputs, inputs) 

 當你把inputs,outputs,exceptional(這裏跟inputs共用)傳給select()後,它返回3個新的list,咱們上面將他們分別賦值爲readable,writable,exceptional, 全部在readable list中的socket鏈接表明有數據可接收(recv),全部在writable list中的存放着你能夠對其進行發送(send)操做的socket鏈接,當鏈接通訊出現error時會把error寫到exceptional列表中。

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.

1
2
3
4
5
6
7
8
9
10
11
12
# Handle inputs
for  in  readable:
 
     if  is  server:
         # A "readable" server socket is ready to accept a connection
         connection, client_address  =  s.accept()
         print  >>sys.stderr,  '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.

1
2
3
4
5
6
7
8
9
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  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.

1
2
3
4
5
6
7
8
9
10
11
else :
     # Interpret empty result as closed connection
     print  >>sys.stderr,  'closing' , client_address,  'after reading no data'
     # Stop listening for input on the connection
     if  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.

1
2
3
4
5
6
7
8
9
10
11
# Handle outputs
for  in  writable:
     try :
         next_msg  =  message_queues[s].get_nowait()
     except  Queue.Empty:
         # No messages waiting so stop checking for writability.
         print  >>sys.stderr,  'output queue for' , s.getpeername(),  'is empty'
         outputs.remove(s)
     else :
         print  >>sys.stderr,  'sending "%s" to %s'  %  (next_msg, s.getpeername())
         s.send(next_msg)

最後,若是在跟某個socket鏈接通訊過程當中出了錯誤,就把這個鏈接對象在inputs\outputs\message_queue中都刪除,再把鏈接關閉掉

1
2
3
4
5
6
7
8
9
10
11
# Handle "exceptional conditions"
for  in  exceptional:
     print  >>sys.stderr,  'handling exceptional condition for' , s.getpeername()
     # Stop listening for input on the connection
     inputs.remove(s)
     if  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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
print  >>sys.stderr,  'connecting to %s port %s'  %  server_address
for  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.

1
2
3
4
5
6
7
8
9
10
11
12
13
for  message  in  messages:
 
     # Send messages on both sockets
     for  in  socks:
         print  >>sys.stderr,  '%s: sending "%s"'  %  (s.getsockname(), message)
         s.send(message)
 
     # Read responses on both sockets
     for  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()

 

 

最後服務器端的完整代碼以下: 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#_*_coding:utf-8_*_
__author__  =  'Alex Li'
 
import  select
import  socket
import  sys
import  queue
 
# Create a TCP/IP socket
server  =  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking( False )
 
# Bind the socket to the port
server_address  =  ( 'localhost' 10000 )
print (sys.stderr,  'starting up on %s port %s'  %  server_address)
server.bind(server_address)
 
# Listen for incoming connections
server.listen( 5 )
 
# Sockets from which we expect to read
inputs  =  [ server ]
 
# Sockets to which we expect to write
outputs  =  [ ]
 
message_queues  =  {}
while  inputs:
 
     # Wait for at least one of the sockets to be ready for processing
     print '\nwaiting for the next event' )
     readable, writable, exceptional  =  select.select(inputs, outputs, inputs)
     # Handle inputs
     for  in  readable:
 
         if  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( False )
             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  not  in  outputs:
                     outputs.append(s)
             else :
                 # Interpret empty result as closed connection
                 print ( 'closing' , client_address,  'after reading no data' )
                 # Stop listening for input on the connection
                 if  in  outputs:
                     outputs.remove(s)   #既然客戶端都斷開了,我就不用再給它返回數據了,因此這時候若是這個客戶端的鏈接對象還在outputs列表中,就把它刪掉
                 inputs.remove(s)     #inputs中也刪除掉
                 s.close()            #把這個鏈接關閉掉
 
                 # Remove message queue
                 del  message_queues[s]
     # Handle outputs
     for  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)
     # Handle "exceptional conditions"
     for  in  exceptional:
         print ( 'handling exceptional condition for' , s.getpeername() )
         # Stop listening for input on the connection
         inputs.remove(s)
         if  in  outputs:
             outputs.remove(s)
         s.close()
 
         # Remove message queue
         del  message_queues[s]

 

selectors模塊

This module allows high-level and efficient I/O multiplexing, built upon the select module primitives. Users are encouraged to use this module instead, unless they want precise control over the OS-level primitives used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import  selectors
import  socket
 
sel  =  selectors.DefaultSelector()
 
def  accept(sock, mask):
     conn, addr  =  sock.accept()   # Should be ready
     print ( 'accepted' , conn,  'from' , addr)
     conn.setblocking( False )
     sel.register(conn, selectors.EVENT_READ, read)
 
def  read(conn, mask):
     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 ( 'closing' , conn)
         sel.unregister(conn)
         conn.close()
 
sock  =  socket.socket()
sock.bind(( 'localhost' 10000 ))
sock.listen( 100 )
sock.setblocking( False )
sel.register(sock, selectors.EVENT_READ, accept)
 
while  True :
     events  =  sel.select()
     for  key, mask  in  events:
         callback  =  key.data
         callback(key.fileobj, mask)
相關文章
相關標籤/搜索