IO 多路复用

就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。

select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。

它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

所有的客户端遍历叫轮询,----事件通知效率高

核心是共享内存和事件通知

epoll简单模型

import socket
import select# 创建套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 设置可以重复使用绑定的信息
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)# 绑定本机信息
s.bind(("",7788))# 变为被动
s.listen(10)# 创建一个epoll对象
epoll = select.epoll()# 测试,用来打印套接字对应的文件描述符
# print(s.fileno())
# print(select.EPOLLIN|select.EPOLLET)# 注册事件到epoll中
# epoll.register(fd[, eventmask])
# 注意,如果fd已经注册过,则会发生异常
# 将创建的套接字添加到epoll的事件监听中
epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET)connections = {}
addresses = {}# 循环等待客户端的到来或者对方发送数据
while True:# epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待epoll_list = epoll.poll()# 对事件进行判断for fd, events in epoll_list:# print fd# print events# 如果是socket创建的套接字被激活if fd == s.fileno():new_socket, new_addr = s.accept()print('有新的客户端到来%s' % str(new_addr))# 将 conn 和 addr 信息分别保存起来connections[new_socket.fileno()] = new_socketaddresses[new_socket.fileno()] = new_addr# 向 epoll 中注册 新socket 的 可读 事件epoll.register(new_socket.fileno(), select.EPOLLIN|select.EPOLLET)# 如果是客户端发送数据elif events == select.EPOLLIN:# 从激活 fd 上接收recvData = connections[fd].recv(1024).decode("utf-8")if recvData:print('recv:%s' % recvData)else:# 从 epoll 中移除该 连接 fdepoll.unregister(fd)# server 侧主动关闭该 连接 fdconnections[fd].close()print("%s---offline---" % str(addresses[fd]))del connections[fd]del addresses[fd]

说明

  • EPOLLIN (可读)
  • EPOLLOUT (可写)
  • EPOLLET (ET模式)

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。

Epoll实现http  linux

import socket
import re
import selectdef service_client(new_socket, request):"""为这个客户端返回数据"""# 1. 接收浏览器发送过来的请求 ,即http请求  # GET / HTTP/1.1# .....# request = new_socket.recv(1024).decode("utf-8")# print(">>>"*50)# print(request)request_lines = request.splitlines()print("")print(">"*20)print(request_lines)# GET /index.html HTTP/1.1# get post put delfile_name = ""ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])if ret:file_name = ret.group(1)# print("*"*50, file_name)if file_name == "/":file_name = "/index.html"# 2. 返回http格式的数据,给浏览器try:f = open("./html" + file_name, "rb")except:response = "HTTP/1.1 404 NOT FOUND\r\n"response += "\r\n"response += "------file not found-----"new_socket.send(response.encode("utf-8"))else:html_content = f.read()f.close()response_body = html_contentresponse_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_bodynew_socket.send(response)def main():"""用来完成整体的控制"""# 1. 创建套接字tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 2. 绑定tcp_server_socket.bind(("", 7890))# 3. 变为监听套接字tcp_server_socket.listen(128)tcp_server_socket.setblocking(False)  # 将套接字变为非堵塞# 创建一个epoll对象epl = select.epoll()# 将监听套接字对应的fd注册到epoll中epl.register(tcp_server_socket.fileno(), select.EPOLLIN)fd_event_dict = dict()while True:fd_event_list = epl.poll()  # 默认会堵塞,直到 os监测到数据到来 通过事件通知方式 告诉这个程序,此时才会解堵塞# [(fd, event), (套接字对应的文件描述符, 这个文件描述符到底是什么事件 例如 可以调用recv接收等)]for fd, event in fd_event_list:# 等待新客户端的链接if fd == tcp_server_socket.fileno():new_socket, client_addr = tcp_server_socket.accept()epl.register(new_socket.fileno(), select.EPOLLIN)fd_event_dict[new_socket.fileno()] = new_socketelif 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]# 关闭监听套接字tcp_server_socket.close()if __name__ == "__main__":main()

web静态服务器-epool

以下代码,支持http的长连接,即使用了Content-Length

import socket
import time
import sys
import re
import selectclass WSGIServer(object):"""定义一个WSGI服务器的类"""def __init__(self, port, documents_root):# 1. 创建套接字self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 2. 绑定本地信息self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.server_socket.bind(("", port))# 3. 变为监听套接字self.server_socket.listen(128)self.documents_root = documents_root# 创建epoll对象self.epoll = select.epoll()# 将tcp服务器套接字加入到epoll中进行监听self.epoll.register(self.server_socket.fileno(), select.EPOLLIN|select.EPOLLET)# 创建添加的fd对应的套接字self.fd_socket = dict()def run_forever(self):"""运行服务器"""# 等待对方链接while True:# epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待epoll_list = self.epoll.poll()# 对事件进行判断for fd, event in epoll_list:# 如果是服务器套接字可以收数据,那么意味着可以进行acceptif fd == self.server_socket.fileno():new_socket, new_addr = self.server_socket.accept()# 向 epoll 中注册 连接 socket 的 可读 事件self.epoll.register(new_socket.fileno(), select.EPOLLIN | select.EPOLLET)# 记录这个信息self.fd_socket[new_socket.fileno()] = new_socket# 接收到数据elif event == select.EPOLLIN:request = self.fd_socket[fd].recv(1024).decode("utf-8")if request:self.deal_with_request(request, self.fd_socket[fd])else:# 在epoll中注销客户端的信息self.epoll.unregister(fd)# 关闭客户端的文件句柄self.fd_socket[fd].close()# 在字典中删除与已关闭客户端相关的信息del self.fd_socket[fd]def deal_with_request(self, request, client_socket):"""为这个浏览器服务器"""if not request:returnrequest_lines = request.splitlines()for i, line in enumerate(request_lines):print(i, line)# 提取请求的文件(index.html)# GET /a/b/c/d/e/index.html HTTP/1.1ret = re.match(r"([^/]*)([^ ]+)", request_lines[0])if ret:print("正则提取数据:", ret.group(1))print("正则提取数据:", ret.group(2))file_name = ret.group(2)if file_name == "/":file_name = "/index.html"# 读取文件数据try:f = open(self.documents_root+file_name, "rb")except:response_body = "file not found, 请输入正确的url"response_header = "HTTP/1.1 404 not found\r\n"response_header += "Content-Type: text/html; charset=utf-8\r\n"response_header += "Content-Length: %d\r\n" % len(response_body)response_header += "\r\n"# 将header返回给浏览器client_socket.send(response_header.encode('utf-8'))# 将body返回给浏览器client_socket.send(response_body.encode("utf-8"))else:content = f.read()f.close()response_body = contentresponse_header = "HTTP/1.1 200 OK\r\n"response_header += "Content-Length: %d\r\n" % len(response_body)response_header += "\r\n"# 将数据返回给浏览器client_socket.send(response_header.encode("utf-8")+response_body)# 设置服务器服务静态资源时的路径
DOCUMENTS_ROOT = "./html"def main():"""控制web服务器整体"""# python3 xxxx.py 7890if len(sys.argv) == 2:port = sys.argv[1]if port.isdigit():port = int(port)else:print("运行方式如: python3 xxx.py 7890")returnprint("http服务器使用的port:%s" % port)http_server = WSGIServer(port, DOCUMENTS_ROOT)http_server.run_forever()if __name__ == "__main__":main()

小总结

I/O 多路复用的特点:

通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。 所以, IO多路复用,本质上不会有并发的功能,因为任何时候还是只有一个进程或线程进行工作,它之所以能提高效率是因为select\epoll 把进来的socket放到他们的 '监视' 列表里面,当任何socket有可读可写数据立马处理,那如果select\epoll 手里同时检测着很多socket, 一有动静马上返回给进程处理,总比一个一个socket过来,阻塞等待,处理高效率。

当然也可以多线程/多进程方式,一个连接过来开一个进程/线程处理,这样消耗的内存和进程切换页会耗掉更多的系统资源。 所以我们可以结合IO多路复用和多进程/多线程 来高性能并发,IO复用负责提高接受socket的通知效率,收到请求后,交给进程池/线程池来处理逻辑。

参考资料

  • 如果想了解下epoll在Linux中的实现过程可以参考:http://blog.csdn.net/xiajun07061225/article/details/9250579

传智播客 Web静态服务器-6-epoll相关推荐

  1. http协议服务器ppt,传智播客内部资料HTTP协议.ppt

    <传智播客内部资料HTTP协议.ppt>由会员分享,可在线阅读,更多相关<传智播客内部资料HTTP协议.ppt(12页珍藏版)>请在人人文库网上搜索. 1.北京传智播客教育 , ...

  2. 基于Java Web的传智播客crm企业管理系统的设计与实现

    项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等.这里根据疫情当下,你 ...

  3. 传智播客微金所项目实战移动web开发

    1.源码笔记 我的源码+笔记(很重要):链接: http://pan.baidu.com/s/1kULKqcJ 感谢传智播客项目相关视频:1.6天 链接: https://pan.baidu.com/ ...

  4. 传智播客php电商项目源码,shop thinkphp写的电子商城代码,原 为传智播客的教学 源码 WEB(ASP,PHP,...) 256万源代码下载- www.pudn.com...

    文件名称: shop下载  收藏√  [ 5  4  3  2  1 ] 开发工具: PHP 文件大小: 6807 KB 上传时间: 2016-04-19 下载次数: 0 提 供 者: 李二帅 详细说 ...

  5. 《传智播客.Net培训.net视频教程》(.net视频asp.net培训传智播客asp.net视频教程开放课程c#视频移动开发winform SQL ADO.Net HTML JavaScript

    本资源重要通知 2011年4月传智播客.Net培训-免费公开课现场视频 [重磅内容]微软移动开发介绍1-早起的鸟儿有食吃.rar 详情 53.2MB [重磅内容]微软移动开发介绍2-windows.p ...

  6. 来传智播客学到的第一天

    第一天上课咱们传智播客给我的感觉很好,让我感觉到了学习的气氛是那么的好,同学之间就和兄弟姐妹一样,尤其是班主任,对我们的关怀是无微不至的 对我们非常负责任,心里是那么的暖,早上早早的就到教室了,看有没 ...

  7. php 传智播客 学习内容

    第一阶段:(PHP+MySQL核心编程) 课程名称 阶段课程 课程内容 学习目标 PHP+MySQL核心编程(21天) PHP基本语法加强 Apache--directory配置段 一个IP和多个域名 ...

  8. 传智播客 .NET面试宝典(2015版)

    .Net工程师面试笔试宝典 培训班常见问题 1.你们会带着我们做完整个完整的项目吗? 答:小的项目会,大的项目则不可能, 1.众所周知,随便拿出一个中等大小的项目,也需要好多个熟练的开发人员开发好多个 ...

  9. 传智播客Java面试宝典 | 张老师尽心整理的面试宝典大全,面试阿里腾讯不成问题。西边人西说测试

    提示:本大全每半月更新一次,请持续保持关注!谢谢! 索取方式:头条或公众号中回复[面试] 从享受生活的角度上来说:"程序员并不是一种最好的职业,我认为两种人可以做程序员,第一,你不做程序员, ...

  10. 《2013传智播客视频》-wmv,avi,mp4.目录

    \!2013-03-14俄罗斯方块\视频\01.复习.avi; \!2013-03-14俄罗斯方块\视频\02 复习.avi; \!2013-03-14俄罗斯方块\视频\03 形状旋转.avi; \! ...

最新文章

  1. vue样式 引入图片_详解Vue.js中引入图片路径的几种方式
  2. C语言成长学习题(十六)
  3. 残缺棋盘的伪代码_伪激光雷达:无人驾驶的立体视觉
  4. ExtJs FormPanel布局
  5. linux查看分析性能以及io的一些命令
  6. RealNetworks CTO:我们追求低复杂度的软解码
  7. [2020多校A层11.25]最大K段和(反悔贪心)
  8. 如何让Excel单元格中的名字分散对齐
  9. CSS position属性---absolute与relative
  10. 最长递增子序列(LIS longest-increment-subsequence)最长连续递增子序列 最大连续子序列和
  11. 史上最全电脑优化小技巧
  12. DTCC 2020 | 阿里云李飞飞:云原生分布式数据库与数据仓库系统点亮数据上云之路
  13. JVM虚拟机基础知识(JVM位置、类加载生命周期、堆、元空间、jvm常用参数)
  14. 如何在windows11系统中打开ie11浏览网页
  15. iOS GPUImage研究六:为视频添加图片水印
  16. word修订模式怎么彻底关闭_如何去掉word修订模式
  17. Android studio 报错Multiple annotations of type `dalvik.annotation.EnclosingClass`
  18. 合并 Excel 的多张工作表Sheet报错:无法在此处粘贴此内容
  19. day17 - Web前端概述
  20. 用chrome浏览器上的Markdown Viewer打开本地的md文件

热门文章

  1. mongodb dbref java_Spring DATA MongoDB @DBref查询,or和and联合查询
  2. python 微信爬虫_PythonWchatScrapy
  3. pymysql安装_jqdatasdk手动安装
  4. 1. MFC编程——变量命名规则
  5. 在 dotnet core (C#)下的颜色渐变
  6. 为什么我离开了管理岗位
  7. 原生体验挡不住!JavaScript开源跨平台框架NativeScript
  8. Jquery 数组操作(转)
  9. Contest 7.21(贪心专练)
  10. centos下apache不解析php