I/O多工及select函數解析及執行個體__函數

來源:互聯網
上載者:User
概述

在進行解釋之前,首先要說明幾個概念:
- 使用者空間和核心空間
- 進程切換
- 進程的阻塞
- 檔案描述符
- 緩衝 I/O

使用者空間與核心空間

現在作業系統都是採用虛擬儲存空間,那麼對32位作業系統而言,它的定址空間(虛擬儲存空間)為4G(2的32次方)。作業系統的核心是核心,獨立於普通的應用程式,可以訪問受保護的記憶體空間,也有訪問底層硬體裝置的所有許可權。為了保證使用者進程不能直接操作核心(kernel),保證核心的安全,操心系統將虛擬空間劃分為兩部分,一部分為核心空間,一部分為使用者空間。針對linux作業系統而言,將最高的1G位元組(從虛擬位址0xC0000000到0xFFFFFFFF),供核心使用,稱為核心空間,而將較低的3G位元組(從虛擬位址0x00000000到0xBFFFFFFF),供各個進程使用,稱為使用者空間。

進程切換

為了控制進程的執行,核心必須有能力掛起正在CPU上啟動並執行進程,並恢複以前掛起的某個進程的執行。這種行為被稱為進程切換。因此可以說,任何進程都是在作業系統核心的支援下啟動並執行,是與核心緊密相關的。

從一個進程的運行轉到另一個進程上運行,這個過程中經過下面這些變化:
1. 儲存處理機上下文,包括程式計數器和其他寄存器。
2. 更新PCB資訊。
3. 把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。
4. 選擇另一個進程執行,並更新其PCB。
5. 更新記憶體管理的資料結構。
6. 恢複處理機上下文。

是很耗資源的,具體的可以參考這篇文章:進程切換

註:進程式控制制塊(Processing Control Block),是作業系統核心中一種資料結構,主要表示進程狀態。其作用是使一個在多道程式環境下不能獨立啟動並執行程式(含資料),成為一個能獨立啟動並執行基本單位或與其它進程並發執行的進程。或者說,OS是根據PCB來對並發執行的進程進行控制和管理的。 PCB通常是系統記憶體佔用區中的一個連續存區,它存放著作業系統用於描述進程情況及控制進程運行所需的全部資訊

進程的阻塞

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

檔案描述符fd

檔案描述符(File descriptor)是電腦科學中的一個術語,是一個用於表述指向檔案的引用的抽象化概念。

檔案描述符在形式上是一個非負整數。實際上,它是一個索引值,指向核心為每一個進程所維護的該進程開啟檔案的記錄表。當程式開啟一個現有檔案或者建立一個新檔案時,核心向進程返回一個檔案描述符。在程式設計中,一些涉及底層的程式編寫往往會圍繞著檔案描述符展開。但是檔案描述符這一概念往往只適用於UNIX、Linux這樣的作業系統。

緩衝 I/O

緩衝 I/O 又被稱作標準 I/O,大多數檔案系統的預設 I/O 操作都是緩衝 I/O。在 Linux 的緩衝 I/O 機制中,作業系統會將 I/O 的資料緩衝在檔案系統的頁緩衝( page cache )中,也就是說,資料會先被拷貝到作業系統核心的緩衝區中,然後才會從作業系統核心的緩衝區拷貝到應用程式的地址空間。

緩衝 I/O 的缺點:
資料在傳輸過程中需要在應用程式地址空間和核心進行多次資料拷貝操作,這些資料拷貝操作所帶來的 CPU 以及記憶體開銷是非常大的。

I/O複用的情況
有如下的情況:當TCP用戶端同時處理兩個輸入:標準輸入和TCP通訊端。我們遇到的問題就是,在客戶阻塞於(標準輸入上的)fgets調用期間,服務進程就會被殺死。伺服器TCP雖然正確地給客戶TCP發送一個FIN,但是既然客戶進程正阻塞於從標準輸入讀入的過程,它將看不到這個EOF,直到從通訊端讀時為止(可能已經過了很長時間)。這樣的進程需要一種預先告知核心的能力,使得核心一旦發現進程指定的一個或多個I/O條件就緒(也就是說已經準備好被讀取,或者描述符已能承接更多的輸出),它就通知進程。這個能力稱為I/O複用(I/O multiplexing),是由select和poll這兩個函數支援的。

I/O複用適用如下情況:
  (1)當客戶處理多個描述字時(一般是互動式輸入和網路套介面),必須使用I/O複用。
  (2)當一個客戶同時處理多個套介面時,而這種情況是可能的,但很少出現。
  (3)如果一個TCP伺服器既要處理監聽套介面,又要處理已串連套介面,一般也要用到I/O複用。
  (4)如果一個伺服器即要處理TCP,又要處理UDP,一般要使用I/O複用。
  (5)如果一個伺服器要處理多個服務或多個協議,一般要使用I/O複用。
  與多進程和多線程技術相比,I/O多工技術的最大優勢是系統開銷小,系統不必建立進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。select通過單進程實現同時處理多個非阻塞的socket串連。 IO模型

Unix下可用的5種I/O模型: 阻塞 I/O(blocking IO) 非阻塞 I/O(nonblocking IO) I/O 多工( IO multiplexing,select和poll) 訊號驅動 I/O( signal driven IO) 非同步 I/O(asynchronous IO)

具體的各個模型內容解釋可以參考《UNIX網路編程》的第六章,這裡僅簡要說明下I/O 多工:

有了I/O 多工( IO multiplexing),我們就可以調用select或poll,阻塞在這兩個系統調用種的某一個之上,而不是阻塞在真正的I/O系統調用上。

我們阻塞於select調用,等待資料通訊端變為可讀。當select返回通訊端可讀這一條件時,我們調用recvfrom把所有資料報複製到應用進程緩衝區。

與I/O複用密切相關的另一種I/O模型是在多線程中使用阻塞式I/O。這種模型與上述模型極為相似,但它沒有使用select阻塞在多個檔案描述符上,而是使用多個線程(每個檔案描述符一個線程),這樣每個線程都可以自由地調用諸如recvfrom之類的阻塞式I/O系統調用了。

所以,I/O 多工特點是通過一種機制一個進程能同時等待多個檔案描述符,而這些檔案描述符(通訊端描述符)其中的任意一個進入讀就緒狀態,select()函數就可以返回。
select函數

select通過單進程實現同時處理多個非阻塞的socket串連。該函數允許進程指示核心等待多個事件中的任何一個發生,並只在一個或多個事件發生或經曆一段指定的時間才喚醒它。

select(rlist, wlist, xlist, timeout=None)

select 函數監視的檔案描述符分3類,分別是writefds、readfds、和exceptfds。調用後select函數會阻塞,直到有描述副就緒(有資料 可讀、可寫、或者有except),或者逾時(timeout指定等待時間,如果立即返回設為null即可),函數返回。當select函數返回後,可以 通過遍曆fdset,來找到就緒的描述符。

作為一個例子,我們可以調用select,告知核心僅在下列情況發生時才返回: 集合{1,4,5}中的任何描述符準備好讀 集合{2,7}中的任何描述符準備好寫 集合{1,4}中的任何描述符有異常條件待處理

已經經曆了10秒

就是說調用select告知核心對哪些描述符(就讀,寫或者異常條件)感興趣及等待多長時間。

select目前幾乎在所有的平台上支援,其良好跨平台支援也是它的一個優點。select的一 個缺點在於單個進程能夠監視的檔案描述符的數量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯核心的方式提升這一限制,但是這樣也會造成效率的降低(原因:通過遍曆檔案描述符來擷取已經就緒的socket。事實上,同時串連的大量用戶端在一時刻可能只有很少的處於就緒狀態,因此隨著監視的描述符數量的增長,其效率也會線性下降。)。 select的一個執行個體

Python的select()方法直接叫用作業系統的IO介面,它監控sockets,open files, and pipes(所有帶fileno()方法的檔案控制代碼)何時變成readable 和writeable, 或者通訊錯誤,select()使得同時監控多個串連變的簡單,並且這比寫一個長迴圈來等待和監控多用戶端串連要高效,因為select直接通過作業系統提供的C的網路介面進行操作,而不是通過Python的解譯器。

其大致流程個人總結如下:
服務端:
(1)建立TCP/IP串連,建立socket
(2)建立通訊列表
(3)對inputs進行主迴圈 Handle inputs Handle outputs Handle “exceptional conditions”

用戶端:
(1)建立串連socket
(2)並發的對服務端進行串連
(3)進行資料的發送和接收

可以參考一篇英文文檔:https://pymotw.com/2/select/ 服務端

#-*- coding: utf-8 -*-import selectimport socketimport sysimport Queue#通過這個demo來理解一下非同步這種設計模式#---------------------------------------------------建立TCP/IP串連# Create a TCP/IP socket"""AF_INET(又稱 PF_INET)是 IPv4 網路通訊協定的通訊端類型,AF_INET6 則是 IPv6 的;而 AF_UNIX 則是 Unix 系統本地通訊常用的Socket類型有兩種:流式Socket(SOCK_STREAM)和資料報式Socket(SOCK_DGRAM)。流式是一種連線導向的Socket,針對於連線導向的TCP服務應用;資料報式Socket是一種不需連線的Socket,對應於不需連線的UDP服務應用。"""server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)"""如果flag為0,則將通訊端設為非阻塞模式,否則將通訊端設為阻塞模式(預設值)。非阻塞模式下,如果調用recv()沒有發現任何資料,或send()調用無法立即發送資料,那麼將引起socket.error異常。"""server.setblocking(0)# Bind the socket to the portserver_address = ('localhost', 12300)print >> sys.stderr, 'starting up on %s port %s' % server_addressserver.bind(server_address)# Listen for incoming connectionsserver.listen(5)#---------------------------------------------------通訊列表#select()方法接收並監控3個通訊列表, 第一個是所有的輸入的data,就是指外部發過來的資料,第2個是監控和接收所有要發出去的data(outgoing data),第3個監控錯誤資訊#第一個列表,所有用戶端的進來的串連和資料將會被server的主迴圈程式放在這個list中處理# Sockets from which we expect to read#開始時僅監視這個server是否有活動,當別人主動去串連的時候才會有活動inputs = [ 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, '\nwaiting for the next event'    #select檢測這幾個列表裡面有沒有就緒的檔案描述符,返回三個列表,即迴圈這三個列表並返回可用的(就緒的)列表    readable, writable, exceptional = select.select(inputs, outputs, inputs)    #---------------------------------------------------select之後的處理    # Handle inputs    for s in readable:        # --------------------------------------------------- 第一種是        """        如果這個socket是main "server" socket,它負責監聽用戶端的串連        如果是一個server,代表有用戶端串連過來了,這是一個新串連,是一個新執行個體        如果這個main server socket出現在readable裡,那代表這是server端已經ready來接收一個新的串連進來了        """        if(s 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)            #把這個新的串連放到input列表中,accept之後不會直接進行recv,避免阻塞(因為後面可能還有串連在等待,如果進行recv則阻塞)            inputs.append(connection)            #字典將socket對象作為key,並為之配一個隊列,即為每一個串連都配一個Queue            # Give the connection a queue for data we want to send            message_queues[connection] = Queue.Queue()            #進行下一次輪循        else:            # ---------------------------------------------------第二種            """            socket是已經建立了的串連,它把資料發了過來,這個時候你就可以通過recv()來接收它發過來的資料,            然後把接收到的資料放到queue裡,這樣你就可以把接收到的資料再傳回給用戶端了            """            data = s.recv(1024)            if data:                # A readable client socket has data                print >> sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) #getpeername()返回與某個通訊端關聯的本地協議地址                message_queues[s].put(data)                # Add output channel for response                # 把串連放到outputs列表中                if s not in outputs:                    outputs.append(s)                    # ---------------------------------------------------當串連已經在outputs列表中            else:                #如果沒有data,需要把對應的連結從列表中刪除,否則會造成while inputs的死迴圈,不斷列印waiting for the next event                # Interpret empty result as closed connection                print >> sys.stderr, '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                """                由於python都是引用,而python有GC機制,                所以del語句作用在變數上,而不是資料對象上,del刪除的是變數,而不是資料                """                del message_queues[s] #刪除變數message_queues[s],解除其對對應資料的引用    # Handle outputs    #迴圈已經準備好了的,可以發資料的串連列表    for s in writable:        """        try:            <語句>        except <name>:            <語句>          #如果在try部份引發了名為'name'的異常,則執行這段代碼        else:            <語句>          #如果沒有異常發生,則執行這段代碼        """        try:            """            Queue模組:            queue.put_nowait():無阻塞的向隊列中新增工作            queue.get_nowait():無阻塞的向隊列中get任務            """            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.upper()) #取到資料進行發送    # Handle "exceptional conditions",處理錯誤清單,例如連結出現通訊錯誤等問題,先在inputs和outputs列表中找到並刪除即可    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]
用戶端
#-*- coding: utf-8 -*-#並發的去連結服務端,本例子是兩個並發的向服務端沒有阻塞的發送資訊#達到了用多線程進行網路通訊的一個效果import socketimport sysmessages = ['This is the message. ',            'It will be sent ',            'in parts.',            ]server_address = ('localhost', 12300)# 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 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()

運行結果:

服務端:

用戶端:

poll

poll的相關內容本人還沒有進行學習瞭解,可以參加如下一些資料及demo: http://www.cnblogs.com/alex3714/articles/5876749.html https://www.zhihu.com/question/20122137/answer/14049112

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.