23 python 實現 select 和 epoll 模型 socket 網絡編程

這裡簡單搞搞 select 和 eopll 的接口開發 ~

select 目前幾乎在所有的平台上支持,其良好跨平台支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一,現在其實更多的人用 epoll,在 python 下 epoll 文檔有點少,就先講究搞搞 select ~

select 的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在 Linux 上一般為 1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。

說點我的理解,要是用煩了多線程的網絡編程,可以試試 select 的模型。

傳遞給 select 的參數是幾個列表,分別表示讀事件、寫事件和錯誤事件。select 方法返回三個列表,其中包含滿足條件的對象(讀、寫和異常)。

服務端的代碼:

 #coding:utf-8import socket,selectimport timeimport os #xiaorui.cchost = "localhost"port = 50000s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.bind((host,port))s.listen(5)while 1:     infds,outfds,errfds = select.select([s,],,,5)     if len(infds) != 0:clientsock,clientaddr = s.acceptbuf = clientsock.recv(8196)if len(buf) != 0:    print (buf)    os.popen(\'sleep 10\').read      clientsock.close #     print "no data coming"  

客戶端的代碼:

 #coding:utf-8import socket,select #xiaorui.cchost = "localhost"port = 50000s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect((host,port))s.send("coming from select client")s.close  

圖片 23.1 pic

一個完成的 select 的例子:

這裡有隊列的概念

 #import selectimport socketimport Queueimport timeimport os #創建 socket 套接字server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)server.setblocking(False) #配置參數server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR  , 1)server_address= (\'192.168.0.101\',9999)server.bind(server_address)server.listen(10)inputs = [server]outputs = message_queues = {} #timeout = 20while inputs:    print "waiting for next event" #    readable , writable , exceptional = select.select(inputs, outputs, inputs, timeout)  最後一個是超時,當前連接要是超過這個時間的話,就會 kill    readable , writable , exceptional = select.select(inputs, outputs, inputs)    # When timeout reached , select return three empty lists    if not (readable or writable or exceptional) :print "Time out ! "break;    for s in readable :if s is server:    #通過 inputs 查看是否有客戶端來    connection, client_address = s.accept    print "    connection from ", client_address    connection.setblocking(0)    inputs.append(connection)    message_queues[connection] = Queue.Queueelse:    data = s.recv(1024)    if data :print " received " , data , "from ",s.getpeernamemessage_queues[s].put(data)# Add output channel for responseif s not in outputs:    outputs.append(s)    else:#Interpret empty result as closed connectionprint "  closing", client_addressif s in outputs :    outputs.remove(s)inputs.remove(s)s.close#清除隊列信息del message_queues[s]    for s in writable:try:    next_msg = message_queues[s].get_nowaitexcept Queue.Empty:    print " " , s.getpeername , \'queue empty\'    outputs.remove(s)else:    print " sending " , next_msg , " to ", s.getpeername    os.popen(\'sleep 5\').read    s.send(next_msg)    for s in exceptional:print " exception condition on ", s.getpeername#stop listening for input on the connectioninputs.remove(s)if s in outputs:    outputs.remove(s)s.close#清除隊列信息del message_queues[s]  

關於 epoll 的方面,大家可以看看這個老外的文檔,寫不錯 ~

select 是輪詢、epoll 是觸發式的,所以 epoll 的效率高。

參考的文檔地址:http://scotdoyle.com/python-epoll-howto.html

下面是用 epoll 實現一個服務端 ~

blog from xiaorui.cc

import socket, selectEOL1 = b\'nn\'EOL2 = b\'nrn\'response  = b\'HTTP/1.0 200 OKrnDate: Mon, 1 Jan 1996 01:01:01 GMTrn\'response += b\'Content-Type: text/plainrnContent-Length: 13rnrn\'response += b\'Hello, world!\'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)serversocket.bind((\'0.0.0.0\', 8080))serversocket.listen(1)serversocket.setblocking(0)epoll = select.epollepoll.register(serversocket.fileno, select.EPOLLIN)try:   connections = {}; requests = {}; responses = {}   while True:      events = epoll.poll(1)      for fileno, event in events: if fileno == serversocket.fileno:    connection, address = serversocket.accept    connection.setblocking(0)    epoll.register(connection.fileno, select.EPOLLIN)    connections[connection.fileno] = connection    requests[connection.fileno] = b\'\'    responses[connection.fileno] = response elif event & select.EPOLLIN:    requests[fileno] += connections[fileno].recv(1024)    if EOL1 in requests[fileno] or EOL2 in requests[fileno]:       epoll.modify(fileno, select.EPOLLOUT)       connections[fileno].setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1)       print(\'-\'*40 + \'n\' + requests[fileno].decode[:-2]) elif event & select.EPOLLOUT:    byteswritten = connections[fileno].send(responses[fileno])    responses[fileno] = responses[fileno][byteswritten:]    if len(responses[fileno]) == 0:       connections[fileno].setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 0)       epoll.modify(fileno, 0)       connections[fileno].shutdown(socket.SHUT_RDWR) elif event & select.EPOLLHUP:    epoll.unregister(fileno)    connections[fileno].close    del connections[fileno]finally:   epoll.unregister(serversocket.fileno) 

Epoll 的最大好處是不會隨著FD的數目增長而降低效率,在 select 中採用輪詢處理,每個 fd 的處理情況,而 epoll 是維護一個隊列,直接看隊列是不是空就可以了。

在這裡也推薦大家用 epoll 寫服務端的東西,當然我自己理解的不夠好,咱們多交流 !!!

本文出自 「峰雲,就她了。」 博客,謝絕轉載!

《Python實戰-從菜鳥到大牛的進階之路》