IO多路複用

Sock(I/O 流)經過服務端Sock
第一種方法就是最傳統的多進程併發模型 (每進來一個新的I/O流會分配一個新的進程管理。)
第二種方法就是I/O多路複用 (單個線程,經過記錄跟蹤每一個I/O流(sock)的狀態,來同時管理多個I/O流 。)python

I/O multiplexing 這裏面的 multiplexing 指的實際上是在單個線程經過記錄跟蹤每個Sock(I/O流)的狀態(對應空管塔裏面的Fight progress strip槽)來同時管理多個I/O流. 發明它的緣由,是儘可能多的提升服務器的吞吐能力。
在同一個線程裏面, 經過撥開關的方式,來同時傳輸多個I/O流安全

select, poll, epoll 都是I/O多路複用的具體的實現
==>epoll好處:去除只能接收1024個連接限制;不會修改傳入參數,指定哪一個sock有哪些數據;線程安全服務器

poll系統調用的原理
先註冊回調函數__poll_wait,再初始化table變量(類型爲struct poll_wqueues),接着拷貝用戶傳入的struct pollfd(其實主要是fd)(瓶頸1),而後輪流調用全部fd對應的poll(把current掛到各個fd對應的設備等待隊列上)(瓶頸2)。在設備收到一條消息(網絡設備)或填寫完文件數據(磁盤設備)後,會喚醒設備等待隊列上的進程,這時current便被喚醒了。網絡

每次調用poll系統調用,操做系統都要把current(當前進程)掛到fd對應的全部設備的等待隊列上,能夠想象,fd多到上千的時候,這樣「掛」法很費事;而每次調用epoll_wait則沒有這麼羅嗦,epoll只在epoll_ctl時把current掛一遍(這第一遍是免不了的)並給每一個fd一個命令「好了就調回調函數」,若是設備有事件了,經過回調函數,會把fd放入rdllist,而每次調用epoll_wait就只是收集rdllist裏的fd就能夠了——epoll巧妙的利用回調函數,實現了更高效的事件驅動模型。併發


1.1通常步驟socket

Create an epoll object——建立1個epoll對象
Tell the epoll object to monitor specific events on specific sockets——告訴epoll對象,在指定的socket上監聽指定的事件
Ask the epoll object which sockets may have had the specified event since the last query——詢問epoll對象,從上次查詢以來,哪些socket發生了哪些指定的事件
Perform some action on those sockets——在這些socket上執行一些操做
Tell the epoll object to modify the list of sockets and/or events to monitor——告訴epoll對象,修改socket列表和(或)事件,並監控
Repeat steps 3 through 5 until finished——重複步驟3-5,直到完成
Destroy the epoll object——銷燬epoll對象函數

1.2相關用法操作系統

import select 導入select模塊線程

epoll = select.epoll() 建立一個epoll對象orm

epoll.register(文件句柄,事件類型) 註冊要監控的文件句柄和事件

事件類型:

  select.EPOLLIN 可讀事件

  select.EPOLLOUT 可寫事件

  select.EPOLLERR 錯誤事件

  select.EPOLLHUP 客戶端斷開事件

epoll.unregister(文件句柄) 銷燬文件句柄

epoll.poll(timeout) 當文件句柄發生變化,則會以列表的形式主動報告給用戶進程,timeout

爲超時時間,默認爲-1,即一直等待直到文件句柄發生變化,若是指定爲1

那麼epoll每1秒彙報一次當前文件句柄的變化狀況,若是無變化則返回空

epoll.fileno() 返回epoll的控制文件描述符(Return the epoll control file descriptor)

epoll.modfiy(fineno,event) fineno爲文件描述符 event爲事件類型 做用是修改文件描述符所對應的事件

epoll.fromfd(fileno) 從1個指定的文件描述符建立1個epoll對象

epoll.close() 關閉epoll對象的控制文件描述符

1.3 例子socket客戶端和服務器
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import socket

#建立客戶端socket對象
clientsocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#服務端IP地址和端口號元組
server_address = ('127.0.0.1',8888)
#客戶端鏈接指定的IP地址和端口號
clientsocket.connect(server_address)

while True:
#輸入數據
data = raw_input('please input:')
#客戶端發送數據
clientsocket.sendall(data)
#客戶端接收數據
server_data = clientsocket.recv(1024)
print '客戶端收到的數據:',server_data
#關閉客戶端socket
clientsocket.close()

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
import select

import Queue

# 建立socket對象
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 設置IP地址複用
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# ip地址和端口號
server_address = ("127.0.0.1", 8888)
# 綁定IP地址
serversocket.bind(server_address)
# 監聽,並設置最大鏈接數
serversocket.listen(10)
print "服務器啓動成功,監聽IP:", server_address
# 服務端設置非阻塞
serversocket.setblocking(False)
# 超時時間
timeout = 10
# 建立epoll事件對象,後續要監控的事件添加到其中
epoll = select.epoll()
# 註冊服務器監聽fd到等待讀事件集合
epoll.register(serversocket.fileno(), select.EPOLLIN)
# 保存鏈接客戶端消息的字典,格式爲{}
message_queues = {}
# 文件句柄到所對應對象的字典,格式爲{句柄:對象}
fd_to_socket = {serversocket.fileno(): serversocket, }

while True:
print "等待活動鏈接......"
# 輪詢註冊的事件集合,返回值爲[(文件句柄,對應的事件),(...),....]
events = epoll.poll(timeout)
if not events:
print "epoll超時無活動鏈接,從新輪詢......"
continue
print "有", len(events), "個新事件,開始處理......"

for fd, event in events:
socket = fd_to_socket[fd]
# 若是活動socket爲當前服務器socket,表示有新鏈接
if socket == serversocket:
connection, address = serversocket.accept()
print "新鏈接:", address
# 新鏈接socket設置爲非阻塞
connection.setblocking(False)
# 註冊新鏈接fd到待讀事件集合
epoll.register(connection.fileno(), select.EPOLLIN)
# 把新鏈接的文件句柄以及對象保存到字典
fd_to_socket[connection.fileno()] = connection
# 以新鏈接的對象爲鍵值,值存儲在隊列中,保存每一個鏈接的信息
message_queues[connection] = Queue.Queue()
# 關閉事件
elif event & select.EPOLLHUP:
print 'client close'
# 在epoll中註銷客戶端的文件句柄
epoll.unregister(fd)
# 關閉客戶端的文件句柄
fd_to_socket[fd].close()
# 在字典中刪除與已關閉客戶端相關的信息
del fd_to_socket[fd]
# 可讀事件
elif event & select.EPOLLIN:
# 接收數據
data = socket.recv(1024)
if data:
print "收到數據:", data, "客戶端:", socket.getpeername()
# 將數據放入對應客戶端的字典
message_queues[socket].put(data)
# 修改讀取到消息的鏈接到等待寫事件集合(即對應客戶端收到消息後,再將其fd修改並加入寫事件集合)
epoll.modify(fd, select.EPOLLOUT)
# 可寫事件
elif event & select.EPOLLOUT:
try:
# 從字典中獲取對應客戶端的信息
msg = message_queues[socket].get_nowait()
except Queue.Empty:
print socket.getpeername(), " queue empty"
# 修改文件句柄爲讀事件
epoll.modify(fd, select.EPOLLIN)
else:
print "發送數據:", data, "客戶端:", socket.getpeername()
# 發送數據
socket.send(msg)

# 在epoll中註銷服務端文件句柄 epoll.unregister(serversocket.fileno()) # 關閉epoll epoll.close() # 關閉服務器socket serversocket.close()

相關文章
相關標籤/搜索