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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章