【问题标题】:Tornado server sending file to remote client blocks serverTornado 服务器将文件发送到远程客户端阻止服务器
【发布时间】:2017-03-29 01:37:33
【问题描述】:

所以想用 Tornado 来实现一个简单的文件下载服务器。这是我目前拥有的代码:

class downloadHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        global data
        self.write(data)
        self.flush()
        self.finish()

def get_buf(path):
    buf = bytearray(os.path.getsize(path))
    with open(path, 'rb') as f:
        f.readinto(buf)
    return bytes(buf)

if __name__ == '__main__':
    data = get_buf('path/to/file')
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[(r"/upgrade", downloadHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

如您所见,我将文件读入字节数组并将其转换为字节对象,这仅在启动服务器之前完成一次。我所做的只是将数据写入远程客户端。文件大小为 2MB。我用“围攻”测试它,结果看起来客户端是一个接一个地接收他们的数据,而不是以并发的方式。

siege http://xxx.xx.xx.xx:8000/upgrade -c5 -r1
** SIEGE 4.0.2
** Preparing 5 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200    20.22 secs: 1969682 bytes ==> GET  /upgrade
HTTP/1.1 200    34.24 secs: 1969682 bytes ==> GET  /upgrade
HTTP/1.1 200    48.24 secs: 1969682 bytes ==> GET  /upgrade
HTTP/1.1 200    62.24 secs: 1969682 bytes ==> GET  /upgrade
HTTP/1.1 200    76.25 secs: 1969682 bytes ==> GET  /upgrade

我检查了tornado文档,我认为self.write()是一个非阻塞调用,我调用了self.flush()。那么是什么阻止了服务器?

我也试过tornado.web.StaticFileHandler,结果差不多。

PS:tornado 是正确的工具吗?如果不是,还有哪些其他替代方法可以满足我的需求?

【问题讨论】:

    标签: python concurrency tornado nonblocking


    【解决方案1】:

    尝试以可能 256 KB 的块的形式写入数据,而不是一次全部写入:

    class downloadHandler(tornado.web.RequestHandler):
        async def get(self):
            chunk_size = 256 * 1024
            for i in range(0, len(data), chunk_size):
                self.write(bytes(data[i:i + chunk_size]))
                await self.flush()
    
            self.finish()
    
    def get_buf(path):
        buf = bytearray(os.path.getsize(path))
        with open(path, 'rb') as f:
            f.readinto(buf)
        return buf
    

    分块写入允许 Tornado 的 IOLoop 重新获得写入之间的控制权(这就是“等待”所做的),因此可以同时执行多项操作。

    请注意,您可以通过将数据保存为字节数组而不是转换为字节来节省一些数据复制。

    我的代码是 Python 3.5+。在 2.7 中,执行:

    @gen.coroutine
    def get(self):
        chunk_size = 256 * 1024
        for i in range(0, len(data), chunk_size):
            self.write(bytes(data[i:i + chunk_size]))
            yield self.flush()
    
        self.finish()
    

    【讨论】:

    • 感谢您的回复,我认为您的解决方案很合理。但是结果还是一样……
    • 好的,现在我改变了测试方式。我启动了多个“围攻”进程,每个进程都启动一个到服务器的连接,并且它现在同时运行。
    猜你喜欢
    • 1970-01-01
    • 2015-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多