這里簡單搞搞 select 和 eopll 的接口開發(fā) ~
select 目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點,事實上從現(xiàn)在看來,這也是它所剩不多的優(yōu)點之一,現(xiàn)在其實更多的人用 epoll,在 python 下 epoll 文檔有點少,就先講究搞搞 select ~
select 的一個缺點在于單個進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在 Linux 上一般為 1024,不過可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制。
說點我的理解,要是用煩了多線程的網(wǎng)絡(luò)編程,可以試試 select 的模型。
傳遞給 select 的參數(shù)是幾個列表,分別表示讀事件、寫事件和錯誤事件。select 方法返回三個列表,其中包含滿足條件的對象(讀、寫和異常)。
服務(wù)端的代碼:
#coding:utf-8
import socket,select
import time
import os
#xiaorui.cc
host = "localhost"
port = 50000
s = 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.accept()
buf = clientsock.recv(8196)
if len(buf) != 0:
print (buf)
os.popen('sleep 10').read()
clientsock.close()
# print "no data coming"
客戶端的代碼:
#coding:utf-8
import socket,select
#xiaorui.cc
host = "localhost"
port = 50000
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port))
s.send("coming from select client")
s.close()
http://wiki.jikexueyuan.com/project/python-actual-combat/images/73.jpg" alt="pic" />
一個完成的 select 的例子:
這里有隊列的概念
#
import select
import socket
import Queue
import time
import os
#創(chuàng)建 socket 套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setblocking(False)
#配置參數(shù)
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 = 20
while inputs:
print "waiting for next event"
# readable , writable , exceptional = select.select(inputs, outputs, inputs, timeout) 最后一個是超時,當(dāng)前連接要是超過這個時間的話,就會 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.Queue()
else:
data = s.recv(1024)
if data :
print " received " , data , "from ",s.getpeername()
message_queues[s].put(data)
# Add output channel for response
if s not in outputs:
outputs.append(s)
else:
#Interpret empty result as closed connection
print " closing", client_address
if 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_nowait()
except 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 connection
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
#清除隊列信息
del message_queues[s]
關(guān)于 epoll 的方面,大家可以看看這個老外的文檔,寫不錯 ~
select 是輪詢、epoll 是觸發(fā)式的,所以 epoll 的效率高。
參考的文檔地址:http://scotdoyle.com/python-epoll-howto.html
下面是用 epoll 實現(xiàn)一個服務(wù)端 ~
blog from xiaorui.cc
import socket, select
EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
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.epoll()
epoll.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的數(shù)目增長而降低效率,在 select 中采用輪詢處理,每個 fd 的處理情況,而 epoll 是維護一個隊列,直接看隊列是不是空就可以了。
在這里也推薦大家用 epoll 寫服務(wù)端的東西,當(dāng)然我自己理解的不夠好,咱們多交流 ?。?!
本文出自 “峰云,就她了?!?博客,謝絕轉(zhuǎn)載!