IO多路复用和异步非阻塞

阅读目录

引入

socket发送http请求

import socket
import requests

# 方式一
ret = requests.get('https://www.baidu.com/s?wd=alex')

# 方式二
client = socket.socket()

# 百度创建连接: 阻塞
client.connect(('www.baidu.com', 80))

# 问百度我要什么?
client.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')

# 我等着接收百度给我的回复
chunk_list=[]
while True:
    try:
        chunk = client.recv(8096)
    except BlockingIOError as e:
        break
    if not chunk:
        break
    chunk_list.append(chunk)

body = b''.join(chunk_list)
print(body.decode('utf-8'))

同时发送多个请求

import socket
import requests

# #################### 解决并发:单线程 ####################
# 方式一
key_list = ['alex', 'db', 'sb']
for item in key_list:
    ret = requests.get('https://www.baidu.com/s?wd=%s' % item)

import socket
import requests

# #################### 解决并发:单线程 ####################
# 方式一
key_list = ['alex', 'db', 'sb']
for item in key_list:
    ret = requests.get('https://www.baidu.com/s?wd=%s' % item)


# 方式二:
def get_data(arg):
    pass


key_list = ['alex', 'db', 'sb']
for item in key_list:
    get_data(item)

# 这种单线程下,即需要等待每一个依次完成
##################### 解决并发:多线程 ####################
import threading

key_list = ['alex','db','sb']
for item in key_list:
     t = threading.Thread(target=get_data,args=(item,))
     t.start()

# 开多线程虽然可以增加子线程来执行,但是同样需要等待,而且线程开多了,反而会变慢

# #################### 解决并发:单线程+IO不等待 ####################
# 怎么知道是IO请求? 怎么知道数据回来了?

socket中的.setblocking()非阻塞

import socket

client = socket.socket()
client.setblocking(False)  # 将原来阻塞的位置变成非阻塞(报错)
# 百度创建连接: 阻塞

try:
    client.connect(('www.baidu.com', 80))  # 执行了但报错了
except BlockingIOError as e:
    pass

# 检测到已经连接成功

# 问百度我要什么?
client.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')

# 我等着接收百度给我的回复
chunk_list = []
while True:
    chunk = client.recv(8096)  # 将原来阻塞的位置变成非阻塞(报错)
    if not chunk:
        break
    chunk_list.append(chunk)

body = b''.join(chunk_list)
print(body.decode('utf-8'))

IO多路复用作用

'''
检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据)(可读/可写)
基于IO多路复用+socket实现并发请求(一个线程100个请求)
     IO多路复用 
     socket非阻塞 

     Twisted框架:基于事件循环实现的异步非阻塞框架
         非阻塞:不等待
         异步:执行完成某个任务后自动调用我给他的函数。

     Python中开源 基于事件循环实现的异步非阻塞框架 Twisted

IO多路复用作用:
检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据)(可读/可写)
基于IO多路复用+socket实现并发请求(一个线程100个请求)
     IO多路复用 
     socket非阻塞 

     Twisted框架:基于事件循环实现的异步非阻塞框架
         非阻塞:不等待
         异步:执行完成某个任务后自动调用我给他的函数。

     Python中开源 基于事件循环实现的异步非阻塞框架 Twisted


操作系统检测socket是否发生变化,有三种模式:
    select:最多1024个socket;循环去检测。
    poll:不限制监听socket个数;循环去检测(水平触发)。
    epoll:不限制监听socket个数;回调方式(边缘触发)。
Python模块:
    select.select 
    select.epoll (windows不支持)


提高并发方案:
- 多进程 
- 多线程 
- 异步非阻塞模块(Twisted) scrapy框架(单线程完成并发)

总结:什么是异步非阻塞?
- 非阻塞,不等待。
比如创建socket对某个地址进行connect,
获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作。
如果设置setblocking(False),以上两个过程就不再等待,但是会报BlockingIOError的错误,只要捕获即可。

- 异步,通知,执行完成之后自动执行回调函数或自动执行某些操作(通知)。
比如做爬虫中向某个地址baidu.com发送请求,当请求执行完成之后自行执行回调函数。
'''

实现

实现 单线程并发 (简单版)

import socket
import select

client1 = socket.socket()
client1.setblocking(False)  # 创建连接: 非阻塞

try:
    client1.connect(('www.baidu.com', 80))
except BlockingIOError as e:
    pass

client2 = socket.socket()
client2.setblocking(False)  # 创建连接: 非阻塞
try:
    client2.connect(('www.sogou.com', 80))
except BlockingIOError as e:
    pass

client3 = socket.socket()
client3.setblocking(False)  # 创建连接: 非阻塞
try:
    client3.connect(('www.oldboyedu.com', 80))
except BlockingIOError as e:
    pass

socket_list = [client1, client2, client3]
conn_list = [client1, client2, client3]

while True:
    rlist, wlist, elist = select.select(socket_list, conn_list, [], 0.005)
    # wlist中表示已经连接成功的socket对象
    # elist表示发生错误,信息
    for sk in wlist:
        if sk == client1:
            sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
        elif sk == client2:
            sk.sendall(b'GET /web?query=fdf HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n')
        else:
            sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n')
        conn_list.remove(sk)
    for sk in rlist:
        chunk_list = []
        while True:
            try:
                chunk = sk.recv(8096)
                if not chunk:
                    break
                chunk_list.append(chunk)
            except BlockingIOError as e:
                break
        body = b''.join(chunk_list)
        # print(body.decode('utf-8'))
        print('------------>', body)
        sk.close()  # 接收完数据,断开与服务器的连接
        socket_list.remove(sk)
    if not socket_list:  # 连接列表为空,则结束
        break

select模块参数详解

在这里插入图片描述

实现 单线程并发 (高级版)

如何将一个“变量、函数”,封装到一个类的对象中?

v = [
    [11, 22],  # 每个都有一个append方法
    [22, 33],  # 每个都有一个append方法
    [33, 44],  # 每个都有一个append方法
]

for item in v:
    print(item.append)
# 实现 用类封装的思想
class Foo(object):
    def __init__(self, data):
        self.row = data

    def append(self, item):
        self.row.append(item)


v = [
    Foo([11, 22]),  # 每个都有一个append方法
    Foo([22, 33]),  # 每个都有一个append方法
    Foo([33, 44]),  # 每个都有一个append方法
    # 直接封装成了Foo的对象,而对象中提供了append方法
]

for item in v:
    print(item.append)  # 这句话就不用改写了

在这里插入图片描述

异步非阻塞

import socket
import select


class Req(object):
    def __init__(self, sk, fun):
        self.sock = sk
        self.fun = fun

    def fileno(self):
        return self.sock.fileno()


class Yb(object):
    def __init__(self):
        self.socket_list = []
        self.conn_list = []

    def add(self, url, func):
        client = socket.socket()
        client.setblocking(False)  # 非阻塞
        try:
            client.connect((url, 80))
        except BlockingIOError as e:
            pass

        obj = Req(client, func)
        self.conn_list.append(obj)
        self.socket_list.append(obj)

    def run(self):
        while True:
            rlist, wlist, elist = select.select(self.socket_list, self.conn_list, [], 0.005)
            # wlist中表示已经连接成功的req对象
            # elist表示发生错误,信息
            for item in wlist:
                item.sock.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n')
                self.conn_list.remove(item)
            for item in rlist:
                chunk_list = []
                while True:
                    try:
                        chunk = item.sock.recv(8096)
                        if not chunk:
                            break
                        chunk_list.append(chunk)
                    except BlockingIOError as e:
                        break
                body = b''.join(chunk_list)
                # print(body.decode('utf-8'))
                # print('------------>', body)
                item.fun(body)
                item.sock.close()  # 接收完数据,断开与服务器的连接
                self.socket_list.remove(item)
            if not self.socket_list:  # 连接列表为空,则结束
                break


def baidu_repsonse(body):
    print('百度下载结果:', body)


def sogou_repsonse(body):
    print('搜狗下载结果:', body)


def oldboyedu_repsonse(body):
    print('老男孩下载结果:', body)


t1 = Yb()
t1.add('www.baidu.com', baidu_repsonse)
t1.add('www.sogou.com', sogou_repsonse)
t1.add('www.oldboyedu.com', oldboyedu_repsonse)
t1.run()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值