V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
xuefu
V2EX  ›  Python

Tornado 中 self.write("hello")出现"Broken pipe Error",如何解决?

  •  1
     
  •   xuefu ·
    xuefu · 2014-09-01 10:43:14 +08:00 · 4196 次点击
    这是一个创建于 3527 天前的主题,其中的信息可能已经有所发展或是发生改变。

    其实这里就是解决微信公众号的5秒重传机制,使得公众号后台能获得充裕的处理时间(5+5+5)s。
    我的做法是(以下针对微信公众号的文本消息):
    要想突破5秒时间的响应限制,可以根据文本消息中唯一值MsgId进行排重。当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包发到开发者填写的URL上进行处理,开发者服务器首先对每个到来的文本消息利用memcached,以MsgId为键,值为1,设置set(MsgId, 1),然后进行响应处理,若处理完成会更改MsgId对应的值为某一字符串。而当这次请求响应无法在5秒内完成,微信服务器将用第一次同样的XML数据包(MsgId相同)发起第二次请求,这样开发者服务器通过get(MsgId)的值进行判断,若get(MsgId)为1,则令其自增incr(MsgId)为2。然后进行等待直到get(MsgId)不为1或2或3。若get(MsgId)不为1或2或3,则直接响应微信服务器,响应完成。第三次请求同理。下面是此想法的实现为代码:

    class WeixinHandler(tornado.web.RequestHandler):
        def post(self):
            # 省略文本解析...
            key = msgId
            if mc.get(key) == None:
                mc.set(key, 1)
            elif mc.get(key) == 1 or mc.get(key) == 2:
                mc.incr(key)
    
            if mc.get(key) == 1:
                # 任务处理
            elif mc.get(key) == 2:
                # 无限等待 mc.get(key) != 2 and mc.get(key) != 3
            elif mc.get(key) == 3:
                if mc.get(key) == 3:
                    mc.set(key, u'任务无法完成')
                else:
                    pass
            else:
                pass
            self.write(mc.get(key))
            mc.delete(key) # 每次请求都会调用,这样的话,一个失败的`write`(上面的
                           # 语句)就会导致其它的请求响应异常了。
    

    这里的问题是对于同一个消息的三次请求,每次都要self.write(),而若微信服务器发起了第二次请求,第一次请求就会在self.write()时出现write error: broken pipe而且接着又调用mc.delete(key)了,这样一来就会导致后续的请求响应乱套了,原本的第二次请求可能又会进行任务处理,可能导致无法响应微信服务器。我尝试用异常进行处理:

    try:
            self.write(mc.get(key))
        except IOError as e:
            print "write error ..."
            return
        mc.delete(key)
    

    却仍然没有用,查看了Tornado中的write函数源代码发现其并未对write error: broken pipe抛出异常,使得try... except ...无法捕获。
    各位大神,这个该如何解决啊,已经困扰我好几个月了。

    9 条回复    2014-09-01 18:03:37 +08:00
    Reset
        1
    Reset  
       2014-09-01 11:13:32 +08:00
    你这样逻辑顺序有问题吧,在1,2,3的循环体内执要 return 或 self.finish() 之类的操作让程序只执行的这里, 而不是最后self.write(),不然每次都会执行 最后两行
    还有看看,self.write() 和 self.finish()的区别
    xuefu
        2
    xuefu  
    OP
       2014-09-01 11:56:43 +08:00
    @Reset 因为不确定哪个请求能完成任务的处理,可能第一个请求就能完成,所以每一个请求都应该在最后要执行self.write()进行响应。self.write()是对POST请求进行IO响应,而self.finish()则会断开连接,两个函数执行完后都可以执行后面的语句(如果有的话)?
    arbipher
        3
    arbipher  
       2014-09-01 12:00:50 +08:00
    (我跑个题,机智的室友用“代码模拟登录”的方式,实现了公众号48小时的处理时间。。。)
    Reset
        4
    Reset  
       2014-09-01 12:15:04 +08:00
    哦,看你说(5+5+5)s 我当作只会在第三次才返回;
    你每个循环内都无法确定自己能完成处理?如果内确定就可以在循环内部返回了
    恩,都会继续执行后面的代码
    关于你说的 “Broken pipe Error” 我自己也没遇到过,大概也是逻辑顺序上的原因(或者线程,如果有用的话)
    你说如果微信服务器发起了第二次请求,第一次请求就会出现这个错误,是第一次的返回在第二次请求之后的时间了么,那就是第一次的连接已经被断开了,但是你还在给这个连接发消息;
    或者如果还没有发起第二次请求的时候就已经有这个错了
    总之我觉得,你每个请求来了之后的处理时间是不是都会很长(大于5s)啊?
    xuefu
        5
    xuefu  
    OP
       2014-09-01 12:42:32 +08:00
    @arbipher 呵呵,这是服务号的高级接口啊。。。
    arbipher
        6
    arbipher  
       2014-09-01 12:50:10 +08:00
    @xuefu 不是的=。=
    如果是高级接口就不用“代码模拟登录”了。。。直接回复就行了
    xuefu
        7
    xuefu  
    OP
       2014-09-01 13:00:25 +08:00
    @Reset 任务处理其实就是一个”代码模拟登录“获取信息,所以处理时间是不确定的,要看网络情况了。。。这么说吧,有这样一种特殊情况:假如第一次请求在4.8s后处理完任务了,然后进行self.write(),但是在0.2s内无法完成write操作(假设write操作需要0.4s)。这样一来当到达5s超时了,微信服务器就会关闭第一次连接,发起第二次请求。而第一次请求的self.write()操作继续执行而对方已关闭连接,从而出现broken pipe。当然这里的第一次请求的write操作也可能只要0.1s就完成了响应,就不会有第二次连接了。

    为了快速响应,我还是希望能够每次请求都进行write,现在就是要通过类似异常处理这样来解决这个问题,可是。。。
    kier
        8
    kier  
       2014-09-01 13:44:36 +08:00   ❤️ 1
    知乎上答了你的问题,去看看吧
    Reset
        9
    Reset  
       2014-09-01 18:03:37 +08:00   ❤️ 1
    @xuefu 既然他不把这个异常抛出来,那你可能就只好预设一个可能的 write 时长,然后计算已经用掉的时间来处理了;最好还是能够把这个异常抛出来处理,可能是在httpserver或者其它那个模块里面吧
    我也帮不上什么了, @kler 说的也是个问题,不过 memcache 似乎没有过期时间的设置

    不抛异常,但又在日志里面输出 “Broken Pipe Error” 如果洁癖的话会让人无法忍受啊
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1721 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 16:40 · PVG 00:40 · LAX 09:40 · JFK 12:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.