解决网络IO阻塞
- 阻塞IO的概念:
- 在我们之前学习的网络通信过程中 我们会发现 recvfrom()/recv()/accept()等相关的函数在进行网络相关数据的获取时会默认阻塞的等待,我们把这种通信方式叫阻塞IO。所以我们产生多任务的需求,进而学习了进程线程等方式
- 第一种解决阻塞IO的方法:
- 解决办法:
- 将socket 套接字设置成非阻塞模式,然后采用轮询遍历的方式判断是否有数据接收
- 效果:
- 当设置为非阻塞的的时候,套接字不会进行阻塞等待而是当数据没有到达的时候会抛出一个异常
- 使用方法:
- 手动将套接字设置成非阻塞的模式
- 例:
- 1.server_socket.setblocking(False) “将所有套接字设置成非阻塞模式”
- 这样就不会阻塞等待一个用户的连接
- 2.将套接字放置到一个可遍历的对象中
- 3.每次循环遍历所有套接字对象,如果有接收数据便执行。
- 这种办法的缺点:
- 当手动设置非阻塞模式的时候,每次都要循环遍历存在的所有的socket是否有数据接收,这样很影响性能,一般解决IO阻塞用下边的方法
- 第二种解决阻塞IO的方法:
- 解决办法:
- 多路IO复用,使用Linux 平台(内核版本2.5+)上著名的模型epoll,select.epoll
- 效果:
- 用单个process就可以同时处理多个网络连接的IO,在epoll模型中,由操作系统负责监听的所有socket,当某个socket有数据到达了,操作系统就通知用户进程。
- 优点:
- 高效:这样比 在用户层面实现的 for 、while不断死循环去检测每个socket的通信状态 的代码高效太多 ,也称轮询。
- 监听的数量多:用轮训一般在1024-2048左右。随着需要轮询的数量增加,轮询效率越低。
- epoll则在监听的socket的数量上限,主要和系统参数相关(硬件所能提供的性能),一般机器可以同时监听5w-10w socket,甚至100w。
- 使用方法:
- 1.server_socket.setblocking(False) 将socket设置为非阻塞模式
- 2.epoll.select.epoll() 创建一个epoll的对象
- 3.epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET) 向epoll中注册新的可读事件
- 4.epoll_list = epoll.poll() epoll进行socket扫描,将有数据的socket,返回socket的描述符和事件类型存储到epoll_list中;未指定时间的话默认阻塞等待;
- 5.for fd, events in epoll_list: 遍历epoll_list ,然后判断socket是哪种信息
- 6.new_socket, new_addr = s.accept()/recvData = connections[fd].recv(1024).decode("utf-8") 对数据进行接收
- 7.epoll.unregister(fd) 删除监视的套接字
- epoll模型说明
- 模式
- EPOLLIN (可读)
- EPOLLOUT (可写)
- EPOLLET (ET模式)
- epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,
- LT模式与ET模式的区别如下:
- LT模式:当epoll检测到描述符事件发⽣并将此事件通知应⽤程序,应⽤程序可以不⽴即处理该事件。下次调⽤epoll时,会再次响应应⽤程序并通知此事件。
- ET模式:当epoll检测到描述符事件发⽣并将此事件通知应⽤程序,应⽤程序必须⽴即处理该事件。如果不处理,下次调⽤epoll时,不会再次响应应⽤程序并通知此事件。
- LT类似于故障不除,红灯不灭,
- ET类似于只说⼀次,不再赘述。
- I/O 多路复用的特点:
- 通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。
- 所以, IO多路复用,本质上不会有并发的功能,因为任何时候 监视任务都是单进程(单线程)模式 进行工作,它之所以能提高效率是因为select / epoll 把进来的socket放到他们的 '监视' 列表里面,当任何socket有可读可写数据立马处理,那如果select.epoll 同时检测着很多socket, 一有动静马上返回给进程处理,比一个一个socket过来,恢复阻塞等待前的情况开始执行处理请求 效率高。当然也可以多线程 方式,一个连接过来开一个线程处理,这样消耗的内存和线程 切换页会耗掉更多的系统资源。
- 所以我们可以结合IO多路复用和多线程来提高性能并发,IO复用负责提高接受socket的通知效率,收到请求后,交给线程池来处理逻辑。
文件描述符
- linux一切皆文件的概念:
- 在UNIX/LINUX中,`⼀切皆⽂件`。普通⽂件是⽂件,⽬录也是⽂件,设备是⽂件,⽹络IO也是⽂件。对于Linux来讲 从⽹络的套接字中读取⼀些数据 和 从⽂件中读取⼀些数据 本质上是⼀样的操作。可以理解为⽂件描述符是对我们读写⽂件资源的⼀个引⽤。我们操作这个⽂件描述符就可以完成对⽂件资源的操作。
- 对于Linux操作系统来讲,文件描述符就是对文件资源的一种描述的符号,是一个无符号整数(0,1,2...)。 一个进程中所能打开的文件描述符是有限的。所以用完记得一定要关闭文件。
- 如何获取文件的描述符
- 以获取socket的描述符为例
- 如果获取⼀个socket对应的⽂件描述符 sock.fileno()
防止IO阻塞的案例:
- Tcp服务器案例