基本事实:
1.cpu的速度远高于io速度
2.IO包括网络访问和本地文件访问。比如requests,urllib等传统的网络库都是同步的IO
3.网络IO大部分的时间都是处于等待的状态,在等待的时候,cpu是空闲的,但是又不能执行其他的操作
阻塞是指调用函数时候当前线程被挂起。
非阻塞是指调用函数时候当前线程不会被挂起,而是立即返回。
同步和异步是逻辑层和业务层面的叫法,阻塞和非阻塞是在调用函数时当前线程的状态。
阻塞方式发起请求
方法一:
import requests
html = requests.get("http://www.baidu.com").text
# 三次握手 建立tcp连接
# 等待服务器响应
print(html)
方法二:
通过socket直接获取html,绕过requests;requests底层是实现的http协议,是阻塞式的IO;socket有非阻赛的方法。
下面代码中是阻塞式的写法
# 阻塞的方式写
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = "www.baidu.com"
client.connect((host, 80)) # 阻塞IO
client.send(
"GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(
"/", host).encode("utf8")
)
data = b""
while 1:
d = client.recv(1024) # 阻塞到有数据
if d:
data += d
else:
break
data = data.decode("utf8")
print(data)
非阻塞方式发起请求
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.setblocking(False) # 设置为非阻塞的方法
host = "www.baidu.com"
try:
client.connect((host, 80))
except BlockingIOError as e:
# 三次握手的过程中 可以做一些其他的事情 不去等待握手成功
# client.connect((host2, 80))
pass
while 1:
try:
client.send(
"GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(
"/", host).encode("utf8")
)
print('send success')
break
except OSError as e:
print('try again')
pass
data = b""
while 1:
try:
d = client.recv(1024) # 阻塞到有数据
except BlockingIOError as e:
continue
if d:
data += d
else:
break
data = data.decode("utf8")
print(data)
非阻塞方式改进
采用事件循环+回调
import socket
from selectors import DefaultSelector, EVENT_WRITE, EVENT_READ
selector = DefaultSelector() # 会自动判断操作系统决定用select 还是epoll
# 利用回调 事件循环
class Fetcher:
def connected(self, key):
selector.unregister(key.fd)
self.client.send(
"GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(
"/", self.host).encode("utf8")
)
selector.register(self.client.fileno(), EVENT_READ, self.readable)
def readable(self, key):
d = self.client.recv(1024)
if d:
self.data += d
else:
selector.unregister(key.fd)
data = self.data.decode("utf8")
print(data)
def get_url(self, url):
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.setblocking(False) # 设置为非阻塞的方法
self.data = b""
self.host = "www.baidu.com"
try:
self.client.connect((self.host, 80))
except BlockingIOError as e:
# 三次握手的过程中 可以做一些其他的事情 不去等待握手成功
# client.connect((host2, 80))
pass
# 向select中注册事件
selector.register(self.client.fileno(), EVENT_WRITE, self.connected)
# 核心:事件循环 一个线程完成
def loop_forever():
while 1:
ready = selector.select() # 得到可以操作的socket,得到可操作性的队列
for key, mask in ready:
call_back = key.data
call_back(key)
if __name__ == '__main__':
fetcher = Fetcher()
url = "http://www.baidu.com"
fetcher.get_url(url)
loop_forever()
面临的问题:
1:回调过深 造成代码难以维护 2:栈撕裂造成异常无法向上跑出 采用协程解决,见后续博客。