通过一个小案例深入理解IO多路复用

假如我们现在有这样一个普通的需求,写一个简单的爬虫来爬取校花网的主页

import requests
import time start = time.time() url = 'http://www.xiaohuar.com/'
result = requests.get(url).text print(result)
print(time.time()-start)

  

  这样子是显然没啥问题的,总共耗时约为6秒

但是有没有办法更进一步优化呢,这里如果需要优化我们首先需要知道一个知识点

就是requests这个模块它底层其实是封装了urllib2和urllib3的,而这两个模块底层其实就是socket

如果需要优化,从requests是实现不了的,那么能不能从socket来呢

如果从socket,又该如何优化呢?

首先我们得知道socket到底做了什么,

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
url = 'www.xiaohuar.com/'
client.connect((url, 80))
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format('/',80).encode('utf8'))
data = b''
while 1:
d = client.recv(1024)
if d:
data +=d
else:
break print(data)

  这里的代码就是上面那个requests版本的代码的底层

  在这一坨代码中,有几个点需要注意

  connect和recv,这两个方法都是阻塞io,也就是说,如果连接不到或者接受不到消息的话,程序就会一直等,等到预期的效果为止。

  这就是阻塞

  阻塞有个很大的弊端,那就是cpu无法得到充分利用,因为等待的时间里,cpu是空闲的,而我们又没有执行其他的操作,那么这段时间我们能不能充分利用起来呢

  答案是肯定的,socket提供了一个非阻塞的办法

  

client.setblocking(False)

  直接运行试试效果

  BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。

  结果是抛出了这个异常,这是因为当变为非阻塞时候,连接校花网的url的时候,三次握手还没建立完成,我们就去执行下一步了

  

try:
client.connect((url, 80))
except BlockingIOError as e:
  #处理其他事情
pass

  那么我们可以这样改,抓到这个异常但是不处理,这样子,我们就能在except后面加入其他的代码了,也就是说cpu发个请求就不管了,然后去执行后面的代码,这样效率就提高了。

  再运行一次。

  OSError: [WinError 10057] 由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。

  又抛出了一个异常,和上面的原理差不多,因为是非阻塞模式

最终代码如下

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.setblocking(False)
url = 'www.xiaohuar.com'
try:
client.connect((url, 80))
except BlockingIOError as e:
pass while 1:
try:
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format('/',80).encode('utf8'))
break
except Exception as e:
pass data = b''
while 1:
try:
d = client.recv(1024)
except Exception as e:
continue
if d:
data += d
else:
break print(data)

  这样子虽然有一段时间更充分利用了cpu 但是代码很乱,很麻烦,其次虽然是非阻塞,但是有两个地方只是把之前的阻塞的时间花费了在循环上,那么有没有更好的办法呢?

这里就要引入IO多路复用的概念了

IO复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(读或者写),都能够通知程序来进行相应的读写操作,但是select,poll和epoll都是同步io,也就是说这个读写过程是阻塞的,而异步io则无需自己进行读写,异步io的实现会负责把数据从内核拷贝到用户内存。

select在windows,OS X, 或者linux都能用,但是select最大监视数量只能为1024

而poll的话其他几乎与select一样,只是突破了最大限制

而epoll就与前面这两个都不一样了,它底层使用了红黑树的数据结构,epoll使用一个文件描述符来管理多个文件描述符,将用户关系的文件描述符的事件存放到内核的一个事件表之中,这样在用户空间和内核空间的copy只需一次。

而poll和select都是才用轮询的方式,所以效率差就在这里体现出来了

最终代码 异步IO

from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
import socket selector = DefaultSelector() class Fetcher(): def send_msg(self, key):
selector.unregister(key.fd)
self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format('/', 80).encode('utf8')) selector.register(self.client.fileno(), EVENT_READ, self.recv) def recv(self, key): d = self.client.recv(1024)
if d:
self.data += d
else:
selector.unregister(key.fd)
print(self.data.decode('utf8')) def get_url(self, url):
self.data = b''
try:
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.connect((url, 80))
except Exception as e:
# 加入另外的逻辑
pass selector.register(self.client.fileno(), EVENT_WRITE, self.send_msg) def loop_forever():
while 1:
ready = selector.select()
for key, mask in ready:
call_back = key.data
call_back(key) if __name__ == '__main__':
fet = Fetcher()
fet.get_url('www.xiaohuar.com')
loop_forever()

  

Python:通过一个小案例深入理解IO多路复用的更多相关文章

  1. 用Python写一个小爬虫吧!

    学习了一段时间的web前端,感觉有点看不清前进的方向,于是就写了一个小爬虫,爬了51job上前端相关的岗位,看看招聘方对技术方面的需求,再有针对性的学习. 我在此之前接触过Python,也写过一些小脚 ...

  2. python 学习笔记12(事件驱动、IO多路复用、异步IO)

    阻塞IO和非阻塞IO.同步IO和异步IO的区别 讨论背景:Linux环境下的network IO. 1.先决条件(几个重要概念) 1.1.用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32 ...

  3. 一文彻底理解IO多路复用

    在讲解IO多路复用之前,我们需要预习一下文件以及文件描述符. 什么是文件 程序员使用I/O最终都逃不过文件. 因为这篇同属于高性能.高并发系列,讲到高性能.高并发就离不开Linux/Unix,因此这里 ...

  4. Python(七)Socket编程、IO多路复用、SocketServer

    本章内容: Socket IO多路复用(select) SocketServer 模块(ThreadingTCPServer源码剖析) Socket socket通常也称作"套接字" ...

  5. Python网络编程(http协议,IO多路复用、select内核监听)

    前言: 什么是IO? 分为IO设备和IO接口两个部分 如Linux系统,I/O操作可以有多种方式 比如DIO(DirectI/O) AIO(AsynchronousI/O异步I/O) Memory-M ...

  6. 用Python爬取斗鱼网站的一个小案例

    思路解析: 1.我们需要明确爬取数据的目的:为了按热度查看主播的在线观看人数 2.浏览网页源代码,查看我们需要的数据的定位标签 3.在代码中发送一个http请求,获取到网页返回的html(需要注意的是 ...

  7. extJs学习基础5 理解mvvm的一个小案例

    今天很是幸运,看到了一位大神的博客,学习了不少的东西.太感谢了.(满满的都是爱啊) 建议去学习这个大神的博客,真心不错. 博客地址:http://blog.csdn.net/column/detail ...

  8. JavaWeb:基于MVC设计模式的一个小案例(一)

    (未经允许,请勿转载,谢谢.) 本案例的处理过程: 客户端发送一个请求给服务器,服务器把这个请求给Servlet,Servlet 获取请求信息,根据请求信息的情况去调用 model (在这里是一个普通 ...

  9. 利用Python完成一个小游戏:随机挑选一个单词,并对其进行乱序,玩家要猜出原始单词

    一 Python的概述以及游戏的内容 Python是一种功能强大且易于使用的编程语言,更接近人类语言,以至于人们都说它是“以思考的速度编程”:Python具备现代编程语言所应具备的一切功能:Pytho ...

随机推荐

  1. Cordova插件相关常用命令

    一,插件相关常用命令   1,查看所有已经安装的插件 1 cordova plugin ls 2,安装插件(以camera插件为例) 1 cordova plugin add cordova-plug ...

  2. maven-appfuse配备步骤

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/qiaqia609/article/details/36231851 maven-appfuse配置步 ...

  3. PAT 甲级 1007. Maximum Subsequence Sum (25) 【最大子串和】

    题目链接 https://www.patest.cn/contests/pat-a-practise/1007 思路 最大子列和 就是 一直往后加 如果 sum < 0 就重置为 0 然后每次 ...

  4. nginx负载均衡设置

    在nginx的配置文件[./conf/nginx.conf]中加入: upstream test.balance123.com { server 192.168.1.223:80; server 19 ...

  5. SpringBoot之外部Tomcat运行Spring Boot项目

    内置tomcat8.5.28 外置的要高于此版本才OK spring boot1.5是访问不了jsp页面的 以后要以2.0版本为主流的

  6. Contiki Process概述

    本文涉及到的Protothread机制知识,在http://www.cnblogs.com/songdechiu/p/5793717.html 一.进程类型 进程类型主要有协同式(cooperativ ...

  7. zabbix 监控 haproxy 记录

    1. 当配置好后,遇到 Value should be a JSON object. 该如何是好? sudo 文件增加: zabbix ALL=(ALL) NOPASSWD: ALL haproxy ...

  8. matlab之细胞数组

    学习matlab的一个博客:https://blog.csdn.net/smf0504/article/details/51814362 Matlab从5.0版开始引入了一种新的数据类型—细胞( ce ...

  9. Java常用四大线程池用法以及ThreadPoolExecutor详解

    为什么用线程池? 1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处-理效率 2.线程并发数量过多,抢占系统资源从而导致阻塞 3.对线程进行一些简单的管理 在Java中,线 ...

  10. NOIP 2016【蚯蚓】

    好吧,我承认我是个智障-- 这道题一眼看上去就是个堆,然而实际上有单调性. 注意到,如果 \(q = 0\) 的话,将蚯蚓的左右两边分开丢进两个队列中,则两个队列都是单调不增的,因为每次取出的蚯蚓长度 ...