引入
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()