事件驅動編程是一種編程範式,這裏程序的執行流由外部事件來決定。它的特色是包含一個事件循環,當外部事件發生時使用回調機制來觸發相應的處理。另外兩種常見的編程範式是(單線程)同步以及多線程編程。python
讓咱們用例子來比較和對比一下單線程、多線程以及事件驅動編程模型。下圖展現了隨着時間的推移,這三種模式下程序所作的工做。這個程序有3個任務須要完成,每一個任務都在等待I/O操做時阻塞自身。阻塞在I/O操做上所花費的時間已經用灰色框標示出來了。程序員
在單線程同步模型中,任務按照順序執行。若是某個任務由於I/O而阻塞,其餘全部的任務都必須等待,直到它完成以後它們才能依次執行。這種明確的執行順序和串行化處理的行爲是很容易推斷得出的。若是任務之間並無互相依賴的關係,但仍然須要互相等待的話這就使得程序沒必要要的下降了運行速度。面試
在多線程版本中,這3個任務分別在獨立的線程中執行。這些線程由操做系統來管理,在多處理器系統上能夠並行處理,或者在單處理器系統上交錯執行。這使得當某個線程阻塞在某個資源的同時其餘線程得以繼續執行。與完成相似功能的同步程序相比,這種方式更有效率,但程序員必須寫代碼來保護共享資源,防止其被多個線程同時訪問。多線程程序更加難以推斷,由於這類程序不得不經過線程同步機制如鎖、可重入函數、線程局部存儲或者其餘機制來處理線程安全問題,若是實現不當就會致使出現微妙且使人痛不欲生的bug。編程
在事件驅動版本的程序中,3個任務交錯執行,但仍然在一個單獨的線程控制中。當處理I/O或者其餘昂貴的操做時,註冊一個回調到事件循環中,而後當I/O操做完成時繼續執行。回調描述了該如何處理某個事件。事件循環輪詢全部的事件,當事件到來時將它們分配給等待處理事件的回調函數。這種方式讓程序儘量的得以執行而不須要用到額外的線程。事件驅動型程序比多線程程序更容易推斷出行爲,由於程序員不須要關心線程安全問題。數組
當咱們面對以下的環境時,事件驅動模型一般是一個好的選擇:緩存
當應用程序須要在任務間共享可變的數據時,這也是一個不錯的選擇,由於這裏不須要採用同步處理。安全
網絡應用程序一般都有上述這些特色,這使得它們可以很好的契合事件驅動編程模型。網絡
Python Select 解析
首先列一下,sellect、poll、epoll三者的區別
select
select最先於1983年出如今4.2BSD中,它經過一個「select()系統」調用 來監控多個文件描述符的數組,當select()返回後,該數組中就緒的文件描述符便會被內核修改標誌位,使得進程能夠得到這些文件描述符從而進行後續的讀寫操做。
(也能夠這麼解釋:這個模型中一些模塊配置的是非阻塞I/O,而後使用阻塞select系統調用來肯定一個I/O描述符什麼時候有操做。使用select調用能夠爲多個描述符提供通知,對於每一個提示符,咱們能夠請求描述符的可寫,可讀以及是否發生錯誤。)
select目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢,事實上從如今看來,這也是它所剩很少的優勢之一。
select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,不過能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制。
另外,select()所維護的存儲大量文件描述符的數據結構,隨着文件描述符數量的增大,其複製的開銷也線性增加。同時,因爲網絡響應時間的延遲使得大量TCP鏈接處於非活躍狀態,但調用select()會對全部socket進行一次線性掃描,因此這也浪費了必定的開銷。
複製開銷線性增加是怎麼回事?
操做系統維護"高速的事件循環的隊列",程序在系統中註冊一個事件後,程序須要隔段時間本身去取結果.這時候操做系統會把全部註冊的任務列表給到程序.返回以後,1000個任務,程序本身循環,找到本身的任務.那麼爲何說隨着文件描述符的增大,複製的開銷也線程增大?
首先咱們知道在Linux系統中每個用戶\每個進程可以打開文件的數量默認是1024,也就是前面說的Linux中單個進程可以監視的文件喵舒服的數量默認限制在1024內.固然這個能夠經過更改/etc/security/limits.conf配置文件來調整.
當一個進程中對兩個文件進行讀寫,每個讀寫的需求就會在操做系統的高速循環事件的數組中註冊一個任務,那麼這個任務提交後操做系統進行io操做,接下來進程會拿着以前的註冊信息隔段時間去問下操做系統,個人任務執行完了沒?操做系統做爲一個公務員,纔不會浪費寶貴的時間,而是高速他全部的信息都是自助查詢的.因爲政府資源有限 ,你從這邊只能拷貝這些信息,而後在你本身的電腦上查吧.
因此進程就得把操做系統的 "高速循環事件任務數組"拷貝過來,在根據本身的註冊號,對這1000個任務或者更多去for循環對比.直到找到本身的號,看看狀態是否是已經就緒了.若是就緒了就把數據取下來,若是沒就緒,只能隔段時間再去問拷貝.當有多個IO操做時,可想而之,這個拷貝的量級固然是線性增加.同時內核的"高速事件循環數組"的大小多是百萬級的.
因此說select的效率是不高的.
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()時便獲得通知。
epoll和程序怎樣交互的?
當程序有IO操做時,向操做系統內核註冊一個IO任務,並添加到"高速事件循環任務數組".epoll在接收進程的註冊一個文件描述符請求後,首先經過epoll_ctl()來註冊一個文件描述符,和select()不一樣的是,他不須要進程調用select()裏相應的方法後纔對全部監控的文件描述符進行掃描.而是一旦基於某個文件描述符就緒時,內核會採用相似callback的回調機制,迅速激活這個文件描述符.
而當有進程調用epoll_wait()方法後,epoll會將已經就緒的文件描述符的映射(注意是映射,而不是全部的數組).這個映射至關於文件句柄,進程能夠直接對這個映射循環,當循環到本身註冊的文件描述符後,經過這個映射就能夠進行讀寫IO操做了.
這裏面還有一個問題?epoll回調機制,激活文件描述符後,只有當相關進程調用epoll_wait()方法纔會獲得通知,而當進程在執行其餘代碼,沒有馬上執行epoll_wait()方法時,操做系統又不能停掉進程如今的動做來執行epoll_wait(),這時操做系統會怎麼處理這個文件描述符呢?操做系統會將這個文件描述符,放到一個專門存放就緒文件描述符的列表裏.等着進程下一次epoll_wait()的時候,經過映射返回給進程.
這個時候,操做系統已經把這個數據緩存在內存裏,但這個緩存不會一直緩存,因此有一個緩衝區,可能幾十K,若是這個緩衝區滿了,它就不接IO操做的文件描述符註冊請求了,內核會告訴進程你等會在發申請,我這邊的緩衝區滿了.那麼會產生什麼效果呢?進程就會阻塞住了.等到進程調用epoll_wait()時,緩存空間減小,這時候其餘進程就能夠正常使用了.
總結: epoll 相對與 select優化了3大點:1.返回給進程的是已就緒的文件描述符的映射,優化了複製開銷. 2.通知的方式由進程觸發系統掃描,epoll優化成文件描述符一旦就緒,內核採用回調機制,激活這個文件描述符,這樣當相關進程在調用epoll_wait()時,便獲得通知.(我以爲若是真是這樣,進程只會在獲得通知後纔會for循環已就緒的文件描述符的映射,至關於節省了不少不必的循環.)
epoll是最流行的一種多路IO異步複用的模型.
上面的知識理解便可,由於沒有人面試的時候讓你寫epoll代碼,可是原理要知道,才能作一個牛逼的開發.
Python select
Python的select()方法直接調用操做系統的IO接口,它監控sockets,open files, and pipes(全部帶fileno()方法的文件句柄)什麼時候變成readable 和writeable, 或者通訊錯誤,select()使得同時監控多個鏈接變的簡單,而且這比寫一個長循環來等待和監控多客戶端鏈接要高效,由於select直接經過操做系統提供的C的網絡接口進行操做,而不是經過Python的解釋器。
接下來經過echo server例子(就是接收socket客戶端發過來的數據併發送給客戶端的例子)要以瞭解select 是如何經過單進程實現同時處理多個非阻塞的socket鏈接的
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 使用select實現異步IO 5 ''' 6 7 import select 8 import socket 9 import sys 10 import queue 11 12 # Create a TCP/IP socket 13 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 建立一個socket實例,定義地址簇爲IPV4,socket類型是TCP 14 server.setblocking(0) # 設置server這個socket實例爲非阻塞 15 # 正是由於咱們要掩飾select實現異步IO模型,因此纔將全部的阻塞操做交由select來控制 16 17 # bind the socket to the port 18 19 server_address = ('localhost',10000) 20 print >> sys.stderr, 'starting up on %s port %s' % server_address 21 server.bind(server_address) 22 23 # Listen for incoming connections 24 server.listen(5)
select()方法接收並監控3個通訊列表, 第一個是全部的輸入的data,就是指外部發過來的數據,第2個是監控和接收全部要發出去的data(outgoing data),第3個監控錯誤信息,接下來咱們須要建立2個列表來包含輸入和輸出信息來傳給select().
1 # Sockets from which we expect to read 2 inputs = [ server ] 3 4 # Sockets to which we expect to write 5 outputs = [ ]
全部客戶端的進來的鏈接和數據將會被server的主循環程序放在上面的list中處理,咱們如今的server端須要等待鏈接可寫(writable)以後才能過來,而後接收數據並返回(所以不是在接收到數據以後就馬上返回),由於每一個鏈接要把輸入或輸出的數據先緩存到queue裏,而後再由select取出來再發出去。
1 # Outgoing message queues (socket:Queue) 2 message_queues = {}
下面是此程序的主循環,調用select()時會阻塞和等待直到新的鏈接和數據進來
1 while inputs: 2 3 # Wait for at least one of the sockets to be ready for processing 4 print >>sys.stderr, '\nwaiting for the next event' 5 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列表中。
Readable list 中的socket 能夠有3種可能狀態,第一種是若是這個socket是main "server" socket,它負責監聽客戶端的鏈接,若是這個main server socket出如今readable裏,那表明這是server端已經ready來接收一個新的鏈接進來了,爲了讓這個main server能同時處理多個鏈接,在下面的代碼裏,咱們把這個main server的socket設置爲非阻塞模式。
1 # Handle inputs 2 for s in readable: 3 4 if s is server: 5 # A "readable" server socket is ready to accept a connection 6 connection, client_address = s.accept() 7 print >>sys.stderr, 'new connection from', client_address 8 connection.setblocking(0) 9 inputs.append(connection) 10 11 # Give the connection a queue for data we want to send 12 message_queues[connection] = Queue.Queue()
第二種狀況是這個socket是已經創建了的鏈接,它把數據發了過來,這個時候你就能夠經過recv()來接收它發過來的數據,而後把接收到的數據放到queue裏,這樣你就能夠把接收到的數據再傳回給客戶端了。
1 else: 2 data = s.recv(1024) 3 if data: 4 # A readable client socket has data 5 print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) 6 message_queues[s].put(data) 7 # Add output channel for response 8 if s not in outputs: 9 outputs.append(s)
第三種狀況就是這個客戶端已經斷開了,因此你再經過recv()接收到的數據就爲空了,因此這個時候你就能夠把這個跟客戶端的鏈接關閉了。
1 else: 2 # Interpret empty result as closed connection 3 print >>sys.stderr, 'closing', client_address, 'after reading no data' 4 # Stop listening for input on the connection 5 if s in outputs: 6 outputs.remove(s) #既然客戶端都斷開了,我就不用再給它返回數據了,因此這時候若是這個客戶端的鏈接對象還在outputs列表中,就把它刪掉 7 inputs.remove(s) #inputs中也刪除掉 8 s.close() #把這個鏈接關閉掉 9 10 # Remove message queue 11 del message_queues[s]
對於writable list中的socket,也有幾種狀態,若是這個客戶端鏈接在跟它對應的queue裏有數據,就把這個數據取出來再發回給這個客戶端,不然就把這個鏈接從output list中移除,這樣下一次循環select()調用時檢測到outputs list中沒有這個鏈接,那就會認爲這個鏈接還處於非活動狀態
1 # Handle outputs 2 for s in writable: 3 try: 4 next_msg = message_queues[s].get_nowait() 5 except Queue.Empty: 6 # No messages waiting so stop checking for writability. 7 print >>sys.stderr, 'output queue for', s.getpeername(), 'is empty' 8 outputs.remove(s) 9 else: 10 print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername()) 11 s.send(next_msg)
最後,若是在跟某個socket鏈接通訊過程當中出了錯誤,就把這個鏈接對象在inputs\outputs\message_queue中都刪除,再把鏈接關閉掉
1 # Handle "exceptional conditions" 2 for s in exceptional: 3 print >>sys.stderr, 'handling exceptional condition for', s.getpeername() 4 # Stop listening for input on the connection 5 inputs.remove(s) 6 if s in outputs: 7 outputs.remove(s) 8 s.close() 9 10 # Remove message queue 11 del message_queues[s]
客戶端
下面的這個是客戶端程序展現瞭如何經過select()對socket進行管理並與多個鏈接同時進行交互,
1 import socket 2 import sys 3 4 messages = [ 'This is the message. ', 5 'It will be sent ', 6 'in parts.', 7 ] 8 server_address = ('localhost', 10000) 9 10 # Create a TCP/IP socket 11 socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM), 12 socket.socket(socket.AF_INET, socket.SOCK_STREAM), 13 ] 14 15 # Connect the socket to the port where the server is listening 16 print >>sys.stderr, 'connecting to %s port %s' % server_address 17 for s in socks: 18 s.connect(server_address) 19 接下來經過循環經過每一個socket鏈接給server發送和接收數據。 20 for message in messages: 21 22 # Send messages on both sockets 23 for s in socks: 24 print >>sys.stderr, '%s: sending "%s"' % (s.getsockname(), message) 25 s.send(message) 26 27 # Read responses on both sockets 28 for s in socks: 29 data = s.recv(1024) 30 print >>sys.stderr, '%s: received "%s"' % (s.getsockname(), data) 31 if not data: 32 print >>sys.stderr, 'closing socket', s.getsockname()
實際使用代碼數據結構
server 端:多線程
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 使用select實現異步IO 5 ''' 6 7 import select 8 import socket 9 import sys 10 import queue 11 12 # Create a TCP/IP socket 13 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 建立一個socket實例,定義地址簇爲IPV4,socket類型是TCP 14 server.setblocking(0) # 設置server這個socket實例爲非阻塞 15 # 正是由於咱們要掩飾select實現異步IO模型,因此纔將全部的阻塞操做交由select來控制 16 17 # bind the socket to the port 18 19 server_address = ('localhost',10000) # 指定socket啓動綁定的IP和port 20 print(sys.stderr, 'starting up on %s port %s' % server_address) # 打印開始啓動socket 的IP和port 21 server.bind(server_address) # 把IP_Port 綁定到server實例 22 23 # Listen for incoming connections 24 server.listen(5) # 啓動server實例,並設置最大等待鏈接數爲5個 25 26 # Sockets from which we expect to read 27 inputs = [ server ] # 建立一個inputs列表,用來存儲server實例 和 進入的新鏈接實例,主要做用是分三種:1.server接收新連接. 2.已建立的鏈接接收數據 3.已存在的鏈接接收到客戶端關閉的信息,以此刪除這個舊鏈接 28 # 這裏要注意下一個概念,socket建立出來的實例,每個實例的方法對於select()方法來講,實例只要調用了任何方法都是有所改變的. 29 # 老師說這裏爲何要把server加入到inputs列表中,除了要使下面的代碼循環下去,還有其餘做用.可是我以爲這裏其實就是爲了讓select監控server這個實例,就是這麼簡單,監控server實例的變化 30 31 # Sockets to which we expect to write 32 outputs = [ ] # 建立select監控的列表2,用於發送數據 33 message_queues = {} # 建立一個字典,字典用來保存每個鏈接的隊列. 鍵值對爲 鏈接實例:隊列 34 account = 1 # 循環計數,從計數中咱們能夠看出select()方法,在何時觸發.經測試爲發現select對不一樣列表的監控規則是不同的. 35 36 while inputs: 37 print(account) 38 # Wait for at least one of the sockets to be ready for processing 39 print( '\nwaiting for the next event') 40 readable, writable, exceptional = select.select(inputs, outputs, inputs) 41 ''' 42 經測試爲發現select對不一樣列表的監控規則是不同的. 43 爲何說select()對它所監控的三個列表的監控規則不同的呢?我先把測試出來的結果列出來: 44 1.對inputs列表只監控列表中的元素的修改狀態(也就是監控元素調用的任何方法都算狀態改變),不監控列表中新加的元素和刪除的元素,也就是說添加或者inputs列表中的元素不會馬上中斷select()方法.必須是新加的元素有改變的狀態 45 2.對outputs 列表監控兩個內容 1個是列表中元素狀態的改變 1個是列表中新加元素 ,也就是說outputs列表中新加元素也會馬上中斷select()的阻塞,列表中的元素改變一樣會中斷.刪除元素不會中斷 46 3.exceptional 這個還不知道,可能和 outputs同樣吧.我以爲理解上面兩個便可. 47 那麼怎麼測試的呢? inputs列表的測試,只須要進行鏈接,不進行數據發送便可.若是新增也會觸發中斷,那麼account=1 的時候鏈接上了,緊接着 就會中斷account =2 的select(),由於inputs列表新加了鏈接.這時代碼會走data=s.recv(1024)在繼續就是刪除這個鏈接了.但實際狀況不是,程序代碼會阻塞在accout=2的那次循環的select()處. 48 outputs 列表的測試,客戶端鏈接後在send一個數據.服務端會創建鏈接後,把數據加入到outputs.append(s)中,此時account = 4 的select()方法就會中斷阻塞,立馬發送給客戶端的操做,發送後至關於outputs列表中的元素執行了s.send()方法,因此會在account =5 時再次中斷select()方法,而後顯示隊列爲空,刪除outputs列表中的S元素. 49 總之: 記住上面3個結論. 50 ''' 51 # Handle inputs 52 for s in readable: # 當select()阻塞中斷了,說明有元素髮生變化.進而先檢查 readable列表 53 54 if s is server: # 若是時server實例發生變化,說明有新的鏈接 55 # A "readable" server socket is ready to accept a connection 56 connection, client_address = s.accept() # 實例化新鏈接 57 print('new connection from', client_address) 58 connection.setblocking(False) # 將新鏈接設置爲阻塞狀態 59 inputs.append(connection) 60 61 # Give the connection a queue for data we want to send 62 message_queues[connection] = queue.Queue() # 爲新鏈接建立一個queue實例 63 else: # 若是readable列表中有其餘對象,說明是已鏈接的實例 64 data = s.recv(1024) # 已鏈接的連接實例處於readable列表中說明有接收數據的請求. 65 if data: # 若是接收數據不爲空 66 # A readable client socket has data 67 print(sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) ) 68 message_queues[s].put(data) # 把接收到的數據,放到本身的queue隊列中 69 # Add output channel for response 70 if s not in outputs: # 把此連接實例加入到outputs列表 ,下次循環到select()時加入到writable 71 outputs.append(s) # 72 else: 73 # Interpret empty result as closed connection # 若是數據爲空,說明客戶端關閉連接了 74 print('closing', client_address, 'after reading no data') 75 # Stop listening for input on the connection 76 if s in outputs: # 客戶端既然斷開了,就把發送的列表也給刪除 77 outputs.remove(s) #既然客戶端都斷開了,我就不用再給它返回數據了,因此這時候若是這個客戶端的鏈接對象還在outputs列表中,就把它刪掉 78 inputs.remove(s) #inputs中也刪除掉, 79 s.close() #把這個鏈接關閉掉 80 81 # Remove message queue 82 del message_queues[s] # 把這個連接的消息隊列也刪掉 83 # Handle outputs 84 for s in writable: # 若是writable列表中有元素,說明有信息要發送 85 try: 86 next_msg = message_queues[s].get_nowait() # 獲得這個連接實例的專用queue隊列 87 except queue.Empty: # 若是沒有得到信息,則把此連接元素從outputs列表中刪除 88 # No messages waiting so stop checking for writability. 89 print('output queue for', s.getpeername(), 'is empty') 90 outputs.remove(s) 91 else: 92 print( 'sending "%s" to %s' % (next_msg, s.getpeername())) # 若是獲得信息那麼發送 93 s.send(next_msg) 94 # Handle "exceptional conditions" 95 for s in exceptional: # 這個就過吧 96 print('handling exceptional condition for', s.getpeername() ) 97 # Stop listening for input on the connection 98 inputs.remove(s) 99 if s in outputs: 100 outputs.remove(s) 101 s.close() 102 103 # Remove message queue 104 del message_queues[s] 105 account +=1
client端:
1 #!/usr/bin/env python3.5 2 #__author__:'ted.zhou' 3 ''' 4 5 ''' 6 __author__ = 'jieli' 7 import socket 8 import sys 9 import time 10 11 # messages = [ 'This is the message. ', 12 # 'It will be sent ', 13 # 'in parts.', 14 # ] 15 messages = ['11'] 16 server_address = ('localhost', 10000) 17 18 # Create a TCP/IP socket 19 # socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM), 20 # socket.socket(socket.AF_INET, socket.SOCK_STREAM), 21 # ] 22 socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM),] 23 24 # Connect the socket to the port where the server is listening 25 print(sys.stderr, 'connecting to %s port %s' % server_address) 26 for s in socks: 27 s.connect(server_address) 28 29 time.sleep(5) 30 31 32 for message in messages: 33 34 # Send messages on both sockets 35 for s in socks: 36 print(sys.stderr, '%s: sending "%s"' % (s.getsockname(), message)) 37 s.send(bytes(message,'utf8')) 38 39 # Read responses on both sockets 40 for s in socks: 41 data = s.recv(1024) 42 print(sys.stderr, '%s: received "%s"' % (s.getsockname(), data)) 43 if not data: 44 print(sys.stderr, 'closing socket', s.getsockname()) 45 s.close()
測試結果,可參考:
<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'> starting up on localhost port 10000 1 waiting for the next event new connection from ('127.0.0.1', 60217) 2 waiting for the next event <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'> received "b'11'" from ('127.0.0.1', 60217) 3 waiting for the next event sending "b'11'" to ('127.0.0.1', 60217) 4 waiting for the next event output queue for ('127.0.0.1', 60217) is empty 5 waiting for the next event closing ('127.0.0.1', 60217) after reading no data 6 waiting for the next event