python + redis长轮询

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/duxiangwushirenfei/article/details/53730477

前言

说起来长轮询也不是什么新鲜概念,不过个人首次用python实现。环境Mac, Python3.5.2, Tornado4.4.2。

http协议

在介绍长轮询前先了解下http协议。
http:超文本传输协议,是网络七层模型中的应用层协议,它是基于TCP/IP协议的。在
1,最早的http协议0.9版本,有且仅有一个GET请求。
2,往后就是http1.0,其引入了GET, POST两种请求,此外规定在每次请求中必须发送请求header–头信息。请求响应也多了状态码,缓存等。重要的是http1.0在建立TCP连接发送请求,响应后就会断开TCP连接。换句话说每次http1.0请求,都会同步新建和关闭TCP连接。
3,http1.1中又了put, patch, delete, header, option等请求。重要的改变是http1.1是默认不关闭TCP连接的,也就是说,三次握手建立了一个TCP连接后可以复用,用来重复发送http请求。
在之前的tornado异步机制浅析中即用socket去发送http请求也提到了1.0和1.1带来的区别。
可是不管怎么发展,一定是client端主动向server请求资源。是否想过,当qq,微信上线是如何收到服务端推送来的未读消息?

长轮询

网购时常有页面聊天室,client端又是怎样获取server推送来的消息呢?
client端可以不停的发送请求去server拿取新消息显示在聊天窗口中。如果1min发送一个请求拿一次那么消息延迟最多1min。如果30s一次,那就可能消息延迟30s。为保证这种即时通信的即时性只能加大请求频率,这样服务器处理的请求数量将极大增多,显然不可取。
长轮询登场:client端只发送一个请求给server,该请求不会立刻返回结果,而是和server端保持了一个连接,当server端收到其他需要推送给此client的消息时,即将内容作为这个http请求结果返回给client。如果始终没有消息待到这个http请求超时后返回,客户端client端会再次发送一个类似的http请求。此种请求轮询拿取信息的方式,称之为长轮询。

和普通http响应相比,server如何在适当的时候(有需要推送给特定客户端的消息进来时)返回这个被挂起的长请求?redis的发布订阅。

Redis

Redis使用率非常高的数据库,数据存在内存之中。通常Redis用来作为缓存,常见有五种数据结构,字符,哈希,数组,集合和有序集合。
http://doc.redisfans.com/
此处用到的是redis的pub/sub功能,即发布/订阅功能。
本地Redis中开两个终端,订阅发布如下:
这里写图片描述

python-tornado实现

服务端代码如下:

import time
import tornadoredis
from tornado import web, httpserver, ioloop, gen
from tornado.options import options, define
from tornado.escape import json_encode

define(name='port', default=8888, type=int)

class LongPollingHandler(web.RequestHandler):
    def initialize(self):
        self.client = tornadoredis.Client()
        self.client.connect()  # 连接到Redis

    @web.asynchronous
    def get(self):
        self.get_data()

    # 订阅Redis的消息
    @gen.engine
    def subscribe(self):
        yield gen.Task(self.client.subscribe, 'test_channel')
        self.client.listen(self.on_message)

    def get_data(self):
        if self.request.connection.stream.closed():
            return

        self.subscribe()

        num = 120  # 设置超时时间为120s
        ioloop.IOLoop.instance().add_timeout(
            time.time() + num,
            lambda: self.on_timeout(num)
        )

    def on_timeout(self, num):
        self.send_data(json_encode({'name': '', 'msg': ''}))
        if self.client.connection.connected():
            self.client.disconnect()

    def send_data(self, data):  # 发送响应
        if self.request.connection.stream.closed():
            return

        self.set_header('Content-Type', 'application/json; charset=UTF-8')
        self.write(data)
        self.finish()

    def on_message(self, msg):  # 收到了Redis的消息
        if msg.kind == 'message':
            print(msg)
            self.send_data(str(msg.body))
        elif msg.kind == 'unsubscribe':
            self.client.disconnect()

    def on_finish(self):
        if self.client.subscribed:
            self.client.unsubscribe('test_channel')


if __name__ == '__main__':
    options.parse_command_line()
    app = web.Application(handlers=[(r"/index", LongPollingHandler)])
    http_server = httpserver.HTTPServer(app)
    http_server.listen(options.port)
    ioloop.IOLoop.instance().start()

启动服务器后,浏览器请求资源:

这里写图片描述

可以看出,此时这个http请求处于挂起状态,当手动去Redis数据库中发布对应订阅频道的消息后结果如下:
这里写图片描述
这样python+redis+tornado的长轮询基本实现。

在实现过程中参考了:
http://www.it610.com/article/597504.htm中的server端代码

猜你喜欢

转载自blog.csdn.net/duxiangwushirenfei/article/details/53730477