tornado使用持久连接,保持一定的连接数,实现端口复用的方法

标签: Python  multiprocessing  进程  python  tornado  持久连接  长连接  端口

 一、应用场景和实现方法

 最近遇到一个与对端交互的特殊场景,对端限制了单个IP的端口连接数量。

如果我频繁的打开和关闭TCP连接,只保证连接的数量小于限定值,对我来说,TCP连接已经关闭,端口也回收了,但是对方的端口还是识别为被占用的状态,这就导致大量的连接被拒绝。

所以我查阅相关资料,实现了持久化连接、端口复用的功能,下面是一个基于Python2.7,用tornado 4.5.3框架写的Demo。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import time
import tornado
import requests
import tornado.httpclient
from multiprocessing import Process
from tornado.httputil import url_concat
from tornado.web import RequestHandler, Application

http_client = None  # 全局连接池,多进程每个进程一个,多线程可复用


class MyHandler(RequestHandler):
    def __init__(self, application, request, **kwargs):
        global http_client
        # 选择curl_httpclient而不是默认simple_httpclient,否则会不停关闭新建连接,连接数量只有一个
        tornado.httpclient.AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
        # 保证force_instance=False,否则会生成大量连接实例,占用大量端口,max_clients表示最大连接数量
        http_client = tornado.httpclient.AsyncHTTPClient(force_instance=False, max_clients=20, defaults=dict(request_timeout=5))
        super(MyHandler, self).__init__(application, request, **kwargs)

    @tornado.gen.coroutine
    def post(self):
        post_data = tornado.escape.json_decode(self.request.body or '{}')
        url = url_concat('https://www.baidu.com', post_data)  # 生成链接
        request = tornado.httpclient.HTTPRequest(
            url,
            headers={'Connection': 'keep-alive'},  # 建议加上keep-alive请求头保证长连接
            request_timeout=5, validate_cert=False
        )  # 生成请求实例
        response = yield tornado.gen.Task(http_client.fetch, request)  # 发起交互请求
        self.set_status(200)
        self.set_header('Content-Type', 'application/json; charset=UTF-8')
        self.finish({'code': 0, 'msg': 'OK', 'status_code': response.code})


def async_app(port):
    application = Application([(r'/test/?', MyHandler)], logging='info', debug=True, xsrf_cookies=False)
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(port)
    tornado.ioloop.IOLoop.instance().start()


def send_test_request():  # 发送测试请求的子进程
    time.sleep(0.5)
    while True:
        for port in range(6205, 6209):
            response = requests.post('http://127.0.0.1:%s/test/' % port, json={'test': 'test'})
            print(response.json())
            time.sleep(0.1)

if __name__ == "__main__":
    request_process = Process(target=send_test_request)
    request_process.start()  # 先启动子进程,否则会被tornado的ioloop阻塞住
    for port in range(6205, 6209):
        Process(target=async_app, args=(port,)).start()
    request_process.join()

持久化连接的关键在于下面两行:

        tornado.httpclient.AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
        http_client = tornado.httpclient.AsyncHTTPClient(force_instance=False, max_clients=20, defaults=dict(request_timeout=5))

第一行的作用是选择curl_httpclient而不是默认的simple_httpclient生成连接池,否则会不停关闭、新建连接,同时连接数量只有一个。

第二行要保证force_instance=False,否则会生成大量连接实例,占用大量端口,max_clients参数表示最大连接数量。

 

二、验证

 我们注释掉第一行再运行Demo:

        # tornado.httpclient.AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
        http_client = tornado.httpclient.AsyncHTTPClient(force_instance=False, max_clients=20, defaults=dict(request_timeout=5))

效果是这样:

可以看到使用了53942、53954、53966三个端口号。

 

然后我们把force_instance改成True:

        tornado.httpclient.AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
        http_client = tornado.httpclient.AsyncHTTPClient(force_instance=True, max_clients=20, defaults=dict(request_timeout=5))

效果是这样:

可以看到使用了很多端口,肯定要被拒绝连接了。

 

最后我们不做修改,看看能不能保证端口复用:

        tornado.httpclient.AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
        http_client = tornado.httpclient.AsyncHTTPClient(force_instance=False, max_clients=20, defaults=dict(request_timeout=5))

 效果是这样:

 可以看到一直都在复用45180、45176、45172、45168这4个端口,每个tornado进程使用一个连接池,每个连接池一直占用一个端口,问题解决了。

 

参考链接:https://www.jianshu.com/p/3cc234198567

原文链接:加载失败,请重新获取