轉載 io多路複用

1 基礎知識回顧

注意:我們下面說的都是Linux環境下,跟Windows不同哈~~~java

1.1 用戶空間和內核空間

  如今操做系統都採用虛擬尋址,處理器先產生一個虛擬地址,經過地址翻譯成物理地址(內存的地址),再經過總線的傳遞,最後處理器拿到某個物理地址返回的字節。python

  對32位操做系統而言,它的尋址空間(虛擬存儲空間)爲4G(2的32次方)。操做系統的核心是內核,獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了保證用戶進程不能直接操做內核(kernel),保證內核的安全,操心繫統將虛擬空間劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。針對linux操做系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱爲內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱爲用戶空間。linux

補充:地址空間就是一個非負整數地址的有序集合。如{0,1,2...}。程序員

1.2 進程上下文切換(進程切換)

  爲了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,並恢復之前掛起的某個進程的執行。這種行爲被稱爲進程切換(也叫調度)。所以能夠說,任何進程都是在操做系統內核的支持下運行的,是與內核緊密相關的。web

  從一個進程的運行轉到另外一個進程上運行,這個過程當中通過下面這些變化
  1. 保存當前進程A的上下文編程

  上下文就是內核再次喚醒當前進程時所須要的狀態,由一些對象(程序計數器、狀態寄存器、用戶棧等各類內核數據結構)的值組成。緩存

  這些值包括描繪地址空間的頁表、包含進程相關信息的進程表、文件表等。
  2. 切換頁全局目錄以安裝一個新的地址空間安全

    ...
  3. 恢復進程B的上下文服務器

  能夠理解成一個比較耗資源的過程。

1.3 進程的阻塞

  正在執行的進程,因爲期待的某些事件未發生,如請求系統資源失敗、等待某種操做的完成、新數據還沒有到達或無新工做作等,則由系統自動執行阻塞原語(Block),使本身由運行狀態變爲阻塞狀態。可見,進程的阻塞是進程自身的一種主動行爲,也所以只有處於運行態的進程(得到CPU),纔可能將其轉爲阻塞狀態。當進程進入阻塞狀態,是不佔用CPU資源的

1.4 文件描述符

  文件描述符(File descriptor)是計算機科學中的一個術語,是一個用於表述指向文件的引用的抽象化概念。

  文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫每每會圍繞着文件描述符展開。可是文件描述符這一律念每每只適用於UNIX、Linux這樣的操做系統。

1.5 直接I/O和緩存I/O

  緩存 I/O 又被稱做標準 I/O,大多數文件系統的默認 I/O 操做都是緩存 I/O。在 Linux 的緩存 I/O 機制中,以write爲例,數據會先被拷貝進程緩衝區,在拷貝到操做系統內核的緩衝區中,而後纔會寫到存儲設備中

緩存I/O的write:

直接I/O的write:(少了拷貝到進程緩衝區這一步)

 

write過程當中會有不少次拷貝,知道數據所有寫到磁盤。好了,準備知識概略複習了一下,開始探討IO模式。

 

2 I/O模式

  對於一次IO訪問這回以read舉例,數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的緩衝區,最後交給進程。因此說,當一個read操做發生時,它會經歷兩個階段
  1. 等待數據準備 (Waiting for the data to be ready)
  2. 將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)

正式由於這兩個階段,linux系統產生了下面五種網絡模式的方案:
  -- 阻塞 I/O(blocking IO)
  -- 非阻塞 I/O(nonblocking IO)
  -- I/O 多路複用( IO multiplexing)
  -- 信號驅動 I/O( signal driven IO)
  -- 異步 I/O(asynchronous IO)

  注:因爲signal driven IO在實際中並不經常使用,因此我這隻說起剩下的四種IO 模型。

2.1 block I/O模型(阻塞I/O)

阻塞I/O模型示意圖:

read爲例:

(1)進程發起read,進行recvfrom系統調用;

(2)內核開始第一階段,準備數據(從磁盤拷貝到緩衝區),進程請求的數據並非一下就能準備好;準備數據是要消耗時間的;

(3)與此同時,進程阻塞(進程是本身選擇阻塞與否),等待數據ing;

(4)直到數據從內核拷貝到了用戶空間,內核返回結果,進程解除阻塞。

也就是說,內核準備數據數據從內核拷貝到進程內存地址這兩個過程都是阻塞的。

 

2.2 non-block(非阻塞I/O模型)

能夠經過設置socket使其變爲non-blocking。當對一個non-blocking socket執行讀操做時,流程是這個樣子:

 

  (1)當用戶進程發出read操做時,若是kernel中的數據尚未準備好;

  (2)那麼它並不會block用戶進程,而是馬上返回一個error,從用戶進程角度講 ,它發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果;

  (3)用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而它能夠再次發送read操做。一旦kernel中的數據準備好了,而且又再次收到了用戶進程的system call;

  (4)那麼它立刻就將數據拷貝到了用戶內存,而後返回。

  因此,nonblocking IO的特色是用戶進程內核準備數據的階段須要不斷的主動詢問數據好了沒有

 

2.3 I/O多路複用

    I/O多路複用實際上就是用select, poll, epoll監聽多個io對象,當io對象有變化(有數據)的時候就通知用戶進程。好處就是單個進程能夠處理多個socket。固然具體區別咱們後面再討論,如今先來看下I/O多路複用的流程:

  (1)當用戶進程調用了select,那麼整個進程會被block;

      (2)而同時,kernel會「監視」全部select負責的socket;

  (3)當任何一個socket中的數據準備好了,select就會返回;

  (4)這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。

  因此,I/O 多路複用的特色是經過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就能夠返回

  這個圖和blocking IO的圖其實並無太大的不一樣,事實上,還更差一些。由於這裏須要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom)。可是,用select的優點在於它能夠同時處理多個connection。

  因此,若是處理的鏈接數不是很高的話,使用select/epoll的web server不必定比使用多線程 + 阻塞 IO的web server性能更好,可能延遲還更大。

  select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。)

  在IO multiplexing Model中,實際中,對於每個socket,通常都設置成爲non-blocking,可是,如上圖所示,整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

 

 2.4 asynchronous I/O(異步 I/O)

  真正的異步I/O很牛逼,流程大概以下:

(1)用戶進程發起read操做以後馬上就能夠開始去作其它的事

(2)而另外一方面,從kernel的角度,當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。

(3)而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了

 

2.5 小結

(1)blocking和non-blocking的區別

  調用blocking IO會一直block住對應的進程直到操做完成,而non-blocking IO在kernel還準備數據的狀況下會馬上返回。

(2)synchronous IO和asynchronous IO的區別

  在說明synchronous IO和asynchronous IO的區別以前,須要先給出二者的定義。POSIX的定義是這樣子的:
    - A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
    - An asynchronous I/O operation does not cause the requesting process to be blocked;

  二者的區別就在於synchronous IO作」IO operation」的時候會將process阻塞。按照這個定義,以前所述的blocking IO,non-blocking IO,IO multiplexing都屬於synchronous IO。

  有人會說,non-blocking IO並無被block啊。這裏有個很是「狡猾」的地方,定義中所指的」IO operation」是指真實的IO操做,就是例子中的recvfrom這個system call。non-blocking IO在執行recvfrom這個system call的時候,若是kernel的數據沒有準備好,這時候不會block進程。可是,當kernel中數據準備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內,進程是被block的。

  而asynchronous IO則不同,當進程發起IO 操做以後,就直接返回不再理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程當中,進程徹底沒有被block。

(3)non-blocking IO和asynchronous IO的區別

  能夠發現non-blocking IO和asynchronous IO的區別仍是很明顯的。

  --在non-blocking IO中,雖然進程大部分時間都不會被block,可是它仍然要求進程去主動的check,而且當數據準備完成之後,也須要進程主動的再次調用recvfrom來將數據拷貝到用戶內存

  --而asynchronous IO則徹底不一樣。它就像是用戶進程將整個IO操做交給了他人(kernel)完成,而後他人作完後發信號通知。在此期間,用戶進程不須要去檢查IO操做的狀態,也不須要主動的去拷貝數據。

 

3 事件驅動編程模型

3.1論事件驅動

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

3.2 看圖說話講事件驅動模型

  在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. 在等待事件到來時,某些任務會阻塞。

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

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

 

4 select/poll/epoll的區別及其Python示例

4.1 select/poll/epoll的區別

  首先前文已述I/O多路複用的本質就是用select/poll/epoll,去監聽多個socket對象,若是其中的socket對象有變化,只要有變化,用戶進程就知道了。

  select是不斷輪詢去監聽的socket,socket個數有限制,通常爲1024個;

  poll仍是採用輪詢方式監聽,只不過沒有個數限制;

  epoll並非採用輪詢方式去監聽了,而是當socket有變化時經過回調的方式主動告知用戶進程。

4.2 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 import select
 2 import socket
 3 import sys
 4 import Queue
 5  
 6 # Create a TCP/IP socket
 7 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 8 server.setblocking(0)
 9  
10 # Bind the socket to the port
11 server_address = ('localhost', 10000)
12 print >>sys.stderr, 'starting up on %s port %s' % server_address
13 server.bind(server_address)
14  
15 # Listen for incoming connections
16 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取出來再發出去。

  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.

1 # Outgoing message queues (socket:Queue)
2 message_queues = {}

  The main portion of the server program loops, calling select() to block and wait for network activity.

  下面是此程序的主循環,調用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列表中。

  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 # 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裏,這樣你就能夠把接收到的數據再傳回給客戶端了。

  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 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()接收到的數據就爲空了,因此這個時候你就能夠把這個跟客戶端的鏈接關閉了。

  A readable socket without data available is from a client that has disconnected, and the stream is ready to be closed.

複製代碼
 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中沒有這個鏈接,那就會認爲這個鏈接還處於非活動狀態

  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 # 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]
複製代碼

4.3 完整的server端和client端示例

  這裏實現了一個server,其功能就是能夠和多個client創建鏈接,每一個client的發過來的數據加上一個response字符串返回給client端~~~

server端:

複製代碼
 1 #! /usr/bin/env python3
 2 # -*- coding:utf-8 -*-
 3 import socket
 4 import select
 5 
 6 sk = socket.socket()
 7 sk.bind(('127.0.0.1', 9000),)
 8 sk.listen(5)
 9 
10 inputs = [sk, ]
11 outputs = []
12 message = {}  # 實現讀寫分離
13 print("start...")
14 
15 while True:
16     # 監聽的inputs中的socket對象內部若是有變化,那麼這個對象就會在rlist
17     # outputs裏有什麼對象,wlist中就有什麼對象
18     # []若是這裏的對象內部出錯,那會把這些對象加到elist中
19     # 1 是超時時間
20     rlist, wlist, elist = select.select(inputs, outputs, [], 1)
21     print(len(inputs), len(outputs))
22 
23     for r in rlist:
24         if r == sk:
25             conn, addr = sk.accept()
26             conn.sendall(b"ok")
27             # 這裏記住是吧conn添加到inputs中去監聽,千萬別寫成r了
28             inputs.append(conn)
29             message[conn] = []
30         else:
31             try:
32                 data = r.recv(1024)
33                 print(data)
34                 if not data:
35                     raise Exception('鏈接斷開')
36                 message[r].append(data)
37                 outputs.append(r)
38             except Exception as e:
39                 inputs.remove(r)
40                 del message[r]
41 
42     for r in wlist:
43         data = str(message[r].pop(), encoding='utf-8')
44         res = data + "response"
45         r.sendall(bytes(res, encoding='utf-8'))
46         outputs.remove(r)
47 # 實現讀寫分離
48 # IO多路複用的本質是用select、poll、epoll(系統底層提供的)來監聽socket對象內部是否有變化
49 # select 是在Win和Linux中都支持額,至關於系統內部維護了一個for循環,缺點是監聽個數有上限(1024),效率不高
50 # poll的監聽個數沒有限制,但仍然用循環,效率不高。
51 # epoll的機制是socket對象變化,主動告訴epoll。而不是輪詢,至關於有個回調函數,效率比前二者高
52 # Nginx就是用epoll。只要IO操做都支持,除開文件操做
53 
54 # 列表刪除指定元素用remove
複製代碼

client端:

複製代碼
 1 #! /usr/bin/env python3
 2 # -*- coding:utf-8 -*-
 3 
 4 import socket
 5 
 6 
 7 sc = socket.socket()
 8 sc.connect(("127.0.0.1", 9000,))
 9 
10 
11 data = sc.recv(1024)
12 print(data)
13 while True:
14     msg = input(">>>:")
15     if msg == 'q':
16         break
17     if len(msg) == 0:
18         continue
19 
20     send_msg = bytes(msg, encoding="utf-8")
21     sc.send(send_msg)
22     res = sc.recv(1024)
23     print(str(res, encoding="utf-8"))
24 sc.close()
複製代碼

  終於寫完了~~~