使用Python的Tornado框架实现一个简单的WebQQ机器人


Posted in Python onApril 24, 2015

我打算将WebQQ单独出来运行, 一开始直接拷贝了pyxmpp2的mainloop, 但是跑起来问题多多, 所以我又研究了利用Tornado进行网络编程(这里), 所以我放弃了Pyxmpp2的mainloop,使用Tornado进行重写

首先放出项目代码
引子

WebQQ协议是一套基于HTTP的QQ协议, 而用Python的urllib2库进行请求太慢, 因为HTTP本身就使用socket请求, 所以改用多路复用I/O模型, 而Tornado简单高效, 看过代码后可以轻松上手.平台兼容性很好, 所以选择Tornado作为网络框架.
原理

首先实现了一个 HTTPStream类, 其主要接口是add_request方法, 它接受一个必选参数:request 是一个 urllib2.Request的实例, 和一个可选参数:readback是一个接受一个urllib2.urlopen(request)返回的Response参数的读取函数, 代码如下:

class HTTPStream(object):
  # 省略若干代码
  def add_request(self, request, readback = None):
    if not isinstance(request, urllib2.Request):
      raise ValueError, "Not a invaid requset"

    # 此处易触发timeout异常, 省略处理异常代码
    sock, data = self.http_sock.make_http_sock_data(request)

    fd = sock.fileno()
    self.fd_map[fd] = sock
    self.fd_request_map[fd] = request
    callback = partial(self._handle_events, request, data, readback)
    self.ioloop.add_handler(fd, callback, IOLoop.WRITE)

HTTPStream.add_request将urllib2.Request的实例解析出一个socket和一个用于socket发送的数据.前面文章介绍过了, tornado.ioloop.IOLoop.add_handler用于将注册socket, 其需要三个参数: socket的文件描述符, 接受文件描述符和事件参数的回调, 和注册的事件.

我们用到的回调是HTTPStream._handle_events:

class HTTPStream(object):
  # 省略若干代码
  def _handle_events(self, request, data, readback, fd, event):
    """ 用于处理Tornado事件
    Arguments:
      `request`  -  urllib.Request
      `data`   -  socket要写入的数据
      `readback` -  读取函数
      以上参数应当使用partial封装然后将此方法作为IOLoop.add_handler的callback
      `fd`    -  IOLoop传递 文件描述符
      `event`   -  IOLoop传递 tornado
    """
    s = self.fd_map[fd]

    if event & IOLoop.READ:
      # 省略错误处理
      resp = self.http_sock.make_response(s, request)
      args = readback(resp)
      s.setblocking(False)
      if args and len(args) == 3:
        t = threading.Thread(target = self.add_delay_request, args = args)
        t.setDaemon(True)
        t.start()

      if args and len(args) == 2:
        self.add_request(*args)
      self.ioloop.remove_handler(fd)

    if event & IOLoop.WRITE:
      s.sendall(data)
      if readback:
        self.ioloop.update_handler(fd, IOLoop.READ)
      else:
        self.ioloop.remove_handler(fd)

    if event & IOLoop.ERROR:
      pass

它接受的参数上面注释写的很清楚, 不做解释, 所以将此方法通过functools.partial封装做为callback传递给tornado.ioloop.IOLoop.add_handler, 并注册为写事件, 以便发送HTTP请求.

HTTPStream._handle_events用于处理事件, 当事件为写时就发送HTTP请求(根据urllib2.Request生成的用于发送的数据), 并判断是否有读取函数, 有则注册读事件, 当事件为读时就从socket中构建一个Response并传递给读取函数, 读取函数会返回3个值, 分别为: 下一个请求, 请求的读取函数(可为None, 为None则只请求不读取), 下一个请求的延迟(多长事件后添加此请求, 可选, 单位为秒)

依据读取函数返回的三个值来确定下一个请求, 并完成一系列的请求. 更加完整的代码请参见文章开头给出的项目代码

HTTPStream.http_sock.make_response执行时会将socket设为阻塞, 因为不设置阻塞会出现httplib.BadStatusLine异常.读取函数执行完毕,重新将socket设置为非阻塞, 并移除此socket(虽然做了这样的处理但是QQ连接时间稍长还是会触发httplib.BadStatusLine异常)

Python 相关文章推荐
浅谈Python的Django框架中的缓存控制
Jul 24 Python
浅析Python 中整型对象存储的位置
May 16 Python
Python 反转字符串(reverse)的方法小结
Feb 20 Python
详解重置Django migration的常见方式
Feb 15 Python
Python后台开发Django会话控制的实现
Apr 15 Python
解决Django中调用keras的模型出现的问题
Aug 07 Python
pycharm运行scrapy过程图解
Nov 22 Python
使用Python进行防病毒免杀解析
Dec 13 Python
详解pycharm连接不上mysql数据库的解决办法
Jan 10 Python
如何定义TensorFlow输入节点
Jan 23 Python
解决python 虚拟环境删除包无法加载的问题
Jul 13 Python
Python解析微信dat文件的方法
Nov 30 Python
Python程序中使用SQLAlchemy时出现乱码的解决方案
Apr 24 #Python
简单说明Python中的装饰器的用法
Apr 24 #Python
使用基于Python的Tornado框架的HTTP客户端的教程
Apr 24 #Python
简单介绍Python的Tornado框架中的协程异步实现原理
Apr 23 #Python
解决Python中由于logging模块误用导致的内存泄露
Apr 23 #Python
粗略分析Python中的内存泄漏
Apr 23 #Python
使用beaker让Facebook的Bottle框架支持session功能
Apr 23 #Python
You might like
防止本地用户用fsockopen DDOS攻击对策
2011/11/02 PHP
PHP禁止个别IP访问网站
2013/10/30 PHP
php+mysql实现数据库随机重排实例
2014/10/17 PHP
解决form中action属性后面?传递参数 获取不到的问题
2017/07/21 PHP
win10 apache配置虚拟主机后localhost无法使用的解决方法
2018/01/27 PHP
输入框的字数时时统计—关于 onpropertychange 和 oninput 使用
2011/10/21 Javascript
jquery插件制作 表单验证实现代码
2012/08/17 Javascript
基于jquery编写的横向自适应幻灯片切换特效的实例代码
2013/08/06 Javascript
Javascript 按位与赋值运算符 (&=)使用介绍
2014/02/04 Javascript
ajax请求乱码的解决方法(中文乱码)
2014/04/10 Javascript
如何正确使用Nodejs 的 c++ module 链接到 OpenSSL
2014/08/03 NodeJs
node.js中的http.request方法使用说明
2014/12/14 Javascript
jquery动态改变div宽度和高度
2015/02/09 Javascript
轻松掌握JavaScript状态模式
2016/09/07 Javascript
谈谈JavaScript中浏览器兼容问题的写法小议
2016/12/17 Javascript
JavaScript原生编写《飞机大战坦克》游戏完整实例
2017/01/04 Javascript
如何正确理解javascript的模块化
2017/03/02 Javascript
详解基于 axios 的 Vue 项目 http 请求优化
2017/09/04 Javascript
微信小程序canvas.drawImage完全显示图片问题的解决
2018/11/30 Javascript
vue项目中axios请求网络接口封装的示例代码
2018/12/18 Javascript
JS快速实现简单计算器
2020/04/08 Javascript
[53:18]Spirit vs Liquid Supermajor小组赛A组 BO3 第三场 6.2
2018/06/03 DOTA
python解析html开发库pyquery使用方法
2014/02/07 Python
python字典改变value值方法总结
2019/06/21 Python
用Python实现最速下降法求极值的方法
2019/07/10 Python
Softmax函数原理及Python实现过程解析
2020/05/22 Python
英国儿童鞋和靴子:Start-Rite
2019/05/06 全球购物
BSTN意大利:德国街头和运动文化高品质商店
2020/12/22 全球购物
介绍JAVA 中的Collection FrameWork(及如何写自己的数据结构)
2014/10/31 面试题
销售总经理岗位职责
2014/03/15 职场文书
2015欢度元旦标语口号
2014/12/09 职场文书
2015年民主生活会发言材料
2014/12/15 职场文书
大学生自荐材料范文
2014/12/30 职场文书
学术会议通知
2015/04/15 职场文书
Pytorch中Softmax和LogSoftmax的使用详解
2021/06/05 Python
vue实现锚点定位功能
2021/06/29 Vue.js