select模型虽好,却有一个缺陷,只能对1024个文件描述符进行监视,虽然可以通过重新编译内核获得更大的监视数量,但这样做还不如将目光投向更高级的epoll模型。select模型中,每一次都需要遍历所有处于监视中的文件描述符,判断他们哪个可写,哪个可读,这样一来,你监视的越多,速度越慢,而在epoll模型中,所有添加到epoll中的事件都会网卡驱动程序建立起回调关系,简言之,如果有一个连接可写,那么这个可写的事件就会报告给你,而你不需要挨个询问他们哪个连接可写,哪个连接可读, tornado框架就使用了epoll模型。
强调一点,本文所用代码只能在linux环境下执行,因为只有linux系统支持epoll模型。
import socket
import select
def start_server(port):
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', port))
#accept队列大小为100
serversocket.listen(100)
serversocket.setblocking(0)
epoll = select.epoll()
#注册一个in事件,等待有数据可读
epoll.register(serversocket.fileno(), select.EPOLLIN)
try:
#保存连接,请求,和响应信息
connections = {}
while True:
#最多等待1秒钟时间,有事件返回事件列表
events = epoll.poll(1)
for fileno, event in events:
#事件的句柄是server
if fileno == serversocket.fileno():
connection, address = serversocket.accept()
#设置为非阻塞的
connection.setblocking(0)
#新建的连接也注册读事件
epoll.register(connection.fileno(), select.EPOLLIN)
connections[connection.fileno()] = connection
#不是server,那就是建立的连接,现在连接可读
elif event & select.EPOLLIN:
data = connections[fileno].recv(1024)
if data:
epoll.modify(fileno, select.EPOLLOUT)
else:
epoll.modify(fileno, 0)
connections[fileno].shutdown(socket.SHUT_RDWR)
del connections[fileno]
elif event & select.EPOLLOUT:
#可写的事件被触发
clientsocket = connections[fileno]
clientsocket.send('收到数据'.encode(encoding='utf-8'))
#需要回写的数据已经写完了,再次注册读事件
epoll.modify(fileno, select.EPOLLIN)
elif event & select.EPOLLHUP:
#被挂起了,注销句柄,关闭连接,这时候,是客户端主动断开了连接
epoll.unregister(fileno)
if fileno in connections:
connections[fileno].close()
del connections[fileno]
finally:
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()
if __name__ == '__main__':
start_server(8801)
本文所采用的是epoll模型的边缘触发,除此以外,还有一个水平触发。
epoll.poll()返回的是所有可操作的文件描述符和事件类型,具体是哪个事件,需要你自己使用if语句逐个进行判断,此外,还需要你自己来保存文件描述符与socket文件之间的映射关系。
获得客户端连接后,需要将这个socket注册读事件,这样,当这个socket发送数据后,下一次调用epoll.poll()就会获得该socket。
客户端示例代码与上一篇一致
import os
import time
import socket
def start_client(addr, port):
PLC_ADDR = addr
PLC_PORT = port
s = socket.socket()
s.connect((PLC_ADDR, PLC_PORT))
count = 0
while True:
msg = '进程{pid}发送数据'.format(pid=os.getpid())
msg = msg.encode(encoding='utf-8')
s.send(msg)
recv_data = s.recv(1024)
print(recv_data.decode(encoding='utf-8'))
time.sleep(3)
count += 1
if count > 20:
break
s.close()
if __name__ == '__main__':
start_client('127.0.0.1', 8801)
启动服务端后,同时启动多个客户端,观察实验效果。
QQ交流群: 211426309