在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。
epoll 是当今 linux 系统采用的方式,一般服务器都是用 epoll 来实现的。epoll 是一种方案,这种方案不使用多进程多线程多协程,而是使用单进程单线程来实现高并发。那我们之前写的单进程单线程非阻塞的程序和epoll有什么区别呢?
我们之前写的程序是建一个列表存储套接字,然后遍历,这种方式叫轮询。明显比较慢。
epoll :是谁收到谁举手,这种方式叫事件通知。
蓝色表示共享内存,绿色表示存储列表,黄色套接字,白色监听套接字。
程序来实现:
import socket
import re
import select
def service_client(new_client_sock,requst):
'''为这个客户服务'''
# 转为列表
requst_lines = requst.splitlines()
# 寻找文件名 GET /index.html HTTP/1.1
# 开头有:GET POST PUT DEL 不一定是GET 所以应该不能用GET匹配。
file_name = ''
ret = re.match(r'[^/]+(/[^ ]*)',requst_lines[0])
if ret:
file_name = ret.group(1)
if file_name == '/ ':
file_name = '/index.html'
# 2.3 准备body,且这里准备的body不能和之前的字符串一样,直接相加,而是要单独发送。
# 找到相对应的.html文件
try:
f = open('./html'+file_name,'rb')
except:
response = 'HTTP/1.1 404 NOT FOUND\r\n'
response += '\r\n'
response += '---file not found--'
else:
html_content = f.read()
f.close()
# 2.返回数据给浏览器,http 格式
response_body = html_content
response_header = 'HTTP/1.1 200 OK\r\n'
response_header += 'Content-Length:%d\r\n' % len(response_body)
response_header += '\r\n'
response = response_header.encode('utf-8') + response_body
# 2.4 发送数据给浏览器
new_client_sock.send((response))
# 3.长连接不能使用close关闭套接字,那么我们怎么知道数据已经发完了呢?
# 在 header 中加入发送数据的长度 Content-Length:
# new_client_sock.close()
def main():
'''用来完成整体的控制'''
# 1.创建套接字
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.绑定
tcp_socket.bind(('', 7890))
# 3.变为监听套接字
tcp_socket.listen(128)
tcp_socket.setblocking(False) # 将套接字变为非堵塞
# 创建一个epoll对象
epl = select.epoll()
# 将监听套接字对应的 fd 注册到 epoll中。
epl.register(tcp_socket.fileno(), select.EPOLLIN)
fd_event_dict = dict()
while True:
fd_event_list = epl.poll() # 默认会堵塞,直到os 检测到数据到来,通过事件通知的方式告诉这个程序,此时才会解堵塞
# [(fd, event),(套接字对应的文字描述符,这个文件描述符到底是什么事件,例如:可以调用recv接收等)]
for fd,enent in fd_event_list:
# 4.等待客户端的链接
if fd == tcp_socket.fileno():
new_client_sock, client_addr = tcp_socket.accept()
epl.register(new_client_sock.fileno(), select.EPOLLIN)
fd_event_dict[new_client_sock.fileno()] = new_client_sock
elif event == select.EPOLLIN:
# 判断已经链接的客户端是否有数据发送过来
recv_data = fd_event_dict[fd].recv(1024).decode('utf-8')
if recv_data:
service_client(fd_event_dict[fd],recv_data)
else:
fd_event_dict[fd].close()
epl.unregister(fd)
del fd_event_dict[fd]
# 4.关闭监听套接字
tcp_socket.close()
if __name__ == '__main__':
main()