【python】asyncio异步socket通信 | asyncio streams编写TCP通信 | asyncio.StreamReader读取长串数据

一、asyncio异步通信

学习博客/文档归类一下放在下面:

官方文档

简单易懂的例子+各个函数详解:Python 使用asyncio tcp
协程等概念解释+github实例:python 异步socket编程
官方文档翻译:asyncio异步IO——Streams详解
详细函数解释:Python asyncio 异步编程(三)

二、解决asyncio.StreamReader读取长串数据

StreamReader一些内部函数功能博主翻译/理解

最近用asyncio做了异步socket tcp通信,在使用asyncio.StreamReader对象读取数据时出现了一点问题。
首先一些基本代码都是学习网上的,客户端代码:

 reader, writer = await asyncio.open_connection(
        '127.0.0.1', 8888)

但我发现如果服务端给客户端发的信息过长,reader.read()函数就不起作用,虽然官网对这个函数的解释是参数什么都不写就会读取所有的数据。网络查了一下也只发现一个解决办法(图中他其实遇到了跟我一样的问题也就是一调用reader.read()就会永久阻塞,并不会返回什么接收到的数据):
在这里插入图片描述
但是我一用yield就出错
后来想了想干脆用at_eof()这个函数吧,但怎么打印它都返回False也就缓冲区不为空。看官方文档对这个函数的解释:

如果缓冲区为空并且 feed_eof() 被调用,则返回 True 。

查了半天也没找到feed_eof()这个函数到底干什么用,但在at_eof()之前调用一下就解决一切问题了。

官网解释:
feed_eof()
Acknowledge the EOF.

具体代码:(但并没有解决根本原因,第一次对方发送消息可以正常接收,但之后发送消息就会报错feed_data after feed_eof的错)

async def recv_msg(self):
	while True:
		data = bytearray()
		while True:
			chunk = await self.reader.read(100)
			if not chunk:
				break
			data += chunk
			
			self.reader.feed_eof()
			if self.reader.at_eof():
				print(data)	#这里就是一个完整的接收数据了

这么一看if not chunk好像也能直接判断缓冲区是不是空的。。。。并且发现await self.reader.read(100)好像不会因为对方没发送数据它就一直阻塞,而是会一直读取一直读取。那await这个功能意义何在?
feed_of()这个函数的发现权当我减少运用了一次await self.reader.read(100)

上述问题的出现我归结于用了feed_eof()这个函数,我现在还不懂这个函数的作用。但不使用它之前我可以确定的是:

  • at_eof()永远返回False

  • await self.reader.read(100)会在对方没有发消息的时候一直阻塞,而不是不断返回None或之类
    所以之后我还是改用self.reader.read(100)这种低级的方法了,只能将参数改大,保证大于对方会发送的最长消息的字节数。如果以后需要传输文件的话就麻烦了需要再想想办法。但其实还有一个弱智办法是让对方先把文件大小传输过来,我再动态改变参数,应该是可行的。
    上述方法又产生一个问题,如果参数设置过大,对方发送信息又过快,接收消息会产生TCP粘包的情况,再找一个解决方法就是使用await self.reader.readuntil(seperator = b"\xFF\xFF"),让发送方在每条发送数据包的后面都加上这个seperator,亲测有效解决问题。

三、一些想整理的

客户端判断是否还有在连接服务器,可以用writer.is_closing(),把writer设为类的属性,这样在不同的async函数里都可以获取,以控制reader函数,或者说整个的循环连接服务器的函数得以在连接断了之后,或是直接退出函数执行,或是开始不断连接服务器

writer函数肯定是放在一个循环中不断写入消息的,这个循环一定要写个asyncio.sleep(),以时不时的暂停一下,不然有可能会陷入死循环。

reader函数则不用asyncio.sleep(),它的await self.reader.readuntil(...)会在消息来的时候才执行,不然就停滞在这里并把执行权力让给其他协程。

客户端/服务器的例子,完整的代码

已标记关键词 清除标记
import aiohttp import asyncio import time import multiprocessing as mp import requests from bs4 import BeautifulSoup import socket import re import pprint import os import pymongo url = 'https://osu.ppy.sh/rankings/mania/performance?page='#+pageNum+'#scores' page = [1, 5] # 开始页数-结束页数 badRequest = {} # pageNum:resCode htmls=[] colls={} headers={'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Encoding':'gb2312,utf-8', 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0', 'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Connection':'Keep-alive' } #way store in mongoDB : collection: {"_id":"1", "Rank":"1","Player Name":"Jakads","Accuracy":"97.59%","Play Count":"" #"Performance":"17288pp"} async def getPages(pageNum): #每1秒获取一个页面当做缓存 conn = aiohttp.TCPConnector(limit=4) global url #global badRequest #global htmls async with aiohttp.ClientSession() as session: try: print('开始get网页,pageNum=',pageNum) async with session.get(url=url +str(pageNum)+'#scores',headers=headers, timeout=10) as res: print(url +str(pageNum)+'#scores') await asyncio.sleep(5) txt=await res.text() resCode= res.status # 如果res不等于200 重试3次 count = 0 #print(res.status_code) while (resCode != 200 and count <= 3): res = await session.get(url=url +str(pageNum)+'#scores',headers=headers, timeout=10) resCode=res.status txt=await res.text() print('restart get') count += 1 if (resCode == 200): print(str(pageNum)+' done') return {str(pageNum):txt} else: print('pageNum : ', pageNum, '返回码 : ', resCode) if(resCode==200): #print(res.url) #writez(res.text) print(str(pageNum) + ' done') return {str(pageNum):txt} else: print( 'pageNum : ', pageNum, '返回码 : ', resCode) return {str(pageNum):resCode} except Exception as e: print(e) return None def findTags(html,startNum): soup = BeautifulSoup(html, features='lxml') tables = soup.findAll('table') # print(len(tables)) for t in tables: sec = 0 #table顺序 for tr in t.tbody.findAll('tr'): # print('sec:',sec) td_sec = 0 #table内顺序 for td in tr.findAll('td'): text = td.get_text().strip() # print(len(text)) if (td_sec == 0): dict = {"rank": text} elif (td_sec == 1): dict.update({"Player Name": text}) elif (td_sec == 2): dict.update({"Accuracy": text}) elif (td_sec == 3): dict.update({"Play Count": text}) elif (td_sec == 4): dict.update({"Performance": text}) elif (td_sec == 5): dict.update({"SS": text}) elif (td_sec == 6): dict.update({"S": text}) elif (td_sec == 7): dict.update({"A": text}) td_sec += 1 #每一次遍历+1 colls[str(startNum+sec)] = dict sec += 1 #每一个用户+1 def writez(col):#写入文本文件tmp.txt if os.path.exists('tmp.txt'): os.remove('tmp.txt') with open('tmp.txt','a',encoding='utf-8') as f: for k,v in col.items(): for k2,v2 in v.items(): f.write(k2+" : "+v2+'\n') def mongoConnection(): conn=pymongo.MongoClient('127.0.0.1',27017) db=conn.osu collection=db.rank return collection def mongoCreateIndex(connect): idx_result = connect.create_index([('rank', pymongo.ASCENDING)], unique=True) return idx_result def mongoInsert(col,connect): tmpList = [] for k, v in col.items(): v.update({"_id":k}) tmpList.append(v) # print('ok') result = connect.insert_many(tmpList) return result def mongoCheckDuplicate(col,connect): for k,v in col.items(): for k2,v2 in v.items(): dictz={"rank":v2} result=connect.find_one(dictz) if(result!=None): res=connect.delete_one(dictz) print('check Duplicate ok') if __name__=='__main__': startTime = time.time() loop=asyncio.get_event_loop() tasks=[] results={} conn=aiohttp.TCPConnector(limit=4) for pageNum in range(page[0], page[1] + 1): tasks.append(asyncio.ensure_future(getPages(pageNum))) finished=loop.run_until_complete(asyncio.wait(tasks)) loop.close() for a in finished: for b in a: if(b.result()!=None): for k,v in b.result().items(): results[str(k)]=str(v) #print(b.result()) #f.write(b.result()) #print('共计完成 ',len(results),'页') osu = mongoConnection() startNum=1 #检索分析网页中的Tag for h in range(page[0], page[1] + 1): findTags(results[str(h)], startNum) startNum += 50 #重复值鉴定,如果重复就在数据库里删除 mongoCheckDuplicate(colls,osu) #插入 try: res=mongoInsert(colls,osu) print('insert res:',res) except Exception as e: print(e) #创建索引 # try: # res=mongoCreateIndex(osu) # print('index res:',res) # except Exception as e: # print(e) print('花费时间 : ', time.time() - startTime, 's') print('ok') 代码如上,,当我使用session.get()时返回码一直为403,换requests.get()就能正常获取网页..初步怀疑是之前爬的太快了被封号了。。但是为什么用requests还能获取呢?有什么办法限速吗 (我用过await asyncio.sleep(),aiohttp.TCPConnector(limit=4))并没有很好的效果。
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页