epoll模型ET和LT

LT是水平触发,ET是边缘触发。都是触发epoll的方式,python下默认就是LT模式,但是可以通过指定select.EPOLLET设置成ET模式。具体的区别可以看以下代码。

LT模式


服务端


import socket, select
import time

serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
serversocket.bind(('0.0.0.0', 9242))
serversocket.listen(1)
serversocket.setblocking(0)

epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN)
LINE_BUF = 5

try:
	connections = {}
	while True:
		print "epolling ..."
		events = epoll.poll(1)
		for fileno, event in events:
			print fileno,events
			if fileno == serversocket.fileno():
				connection, address = serversocket.accept()
				print "accept successfully..."
				connection.setblocking(0)
				epoll.register(connection.fileno(), select.EPOLLIN)
				connections[connection.fileno()] = connection
			elif event & select.EPOLLIN:
				data = connections[fileno].recv(LINE_BUF)
				print "recv data ..."
				print data
				time.sleep(1)
			elif event & select.EPOLLOUT:
				pass;
			elif event & select.EPOLLHUP:
				epoll.unregister(fileno)
				connections[fileno].close()
				del connections[fileno]
finally:
	epoll.unregister(serversocket.fileno())
	epoll.close()
	serversocket.close()

客户端


#!/usr/bin/python

'''
	tcp socket client
'''
import os
import time
import socket

def send_tcp():
	address = ('127.0.0.1', 9242)
	# create a TCP socket
	try :
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		print 'Socket created'
	except socket.error, msg :
		print 'Failed to create socket. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
		sys.exit()

	# create a TCP socket
	try :
		s.connect(address)
		print 'Connect successfully'
	except socket.error, msg :
		print 'Failed to Connect . Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
		sys.exit()

	a = raw_input("Please press any key to continue")
	s.send('123456789012345')

	while True:
		time.sleep(1)
		print "sleeping..."
		pass
	s.close()

if __name__ == '__main__':
	send_tcp()

下面解释下服务端和客户端:
服务端是采用epoll模型的LT模式,每次读取的buffer大小是5字节。客户端去连接服务端,然后按任意建发送了15个字节的buffer,然后进入sleep。下面分步骤解释。

1.收先客户端发起连接后,服务端收到(SYN包),epoll返回连接的句柄,一旦次句柄和默认主socket句柄相等就意味是是一个连接请求。服务端accept请求,并且返回新的连接句柄
2.把连接的新句柄设置感兴趣的监听事件,select.EPOLLIN是可读,默认是LT模式,然后注册到epoll的监听的句柄集合。
3.客户端按下任意键发送了15个字节的数据包,epoll模型返回可读事件的句柄,比较事件的类型后调用recv函数,读取5个字节。
4.epoll发现数据buffer还是readable,继续返回可读句柄,进程又读了5个字节,依次到读取完毕。
5.客户端关闭连接(CTRL+C),这时候服务端epoll还是返回事件相应的句柄,返回的还是EPOLLIN可读事件(没明白!)

ET模式


只需要将上面 server 代码中
epoll.register(connection.fileno(), select.EPOLLIN)
修改为
epoll.register(connection.fileno(), select.EPOLLIN|select.EPOLLET)

再看下执行步骤

1.收先客户端发起连接后,服务端收到(SYN包),epoll返回连接的句柄,一旦次句柄和默认主socket句柄相等就意味是是一个连接请求。服务端accept请求,并且返回新的连接句柄(同上)
2.把连接的新句柄设置感兴趣的监听事件,select.EPOLLIN是可读,默认是LT模式,然后注册到epoll的监听的句柄集合。(同上)
3.客户端按下任意键发送了15个字节的数据包,epoll模型返回可读事件的句柄,比较事件的类型后调用recv函数,读取5个字节。(同上)
4.epoll发现数据buffer还是readable,这时候不再返回,这也就是和LT模型的区别!
5.客户端关闭连接(CTRL+C),于是对于句柄的事件状态变化了,这时候服务端epoll返回相应的句柄,返回的还是EPOLLIN可读事件,再次读取5个字节。这时候其实服务端还是有5个字节再内核的buffer里面了,但是却没机会读了。

总结LT和ET区别


LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket(不能深刻理解).在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

ET (edge-triggered)是高速工作方式,只支持no-block socket(如果block,那么就导致进程阻塞,再也返回不了!)。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

LT叫水平触发的意思和数字电路很像,只要电压是+1或者-1的时候都会触发,对应内核里面就是readable或者writeable。ET叫边缘触发是因为只有在电路电平跳转的时候才能触发,从readable变成writeable,从writeable变成readable这类。

标签:Linux, Python, epoll

评论已关闭