美文网首页
flask-sockets小demo在nginx+gunicor

flask-sockets小demo在nginx+gunicor

作者: 晓函 | 来源:发表于2021-04-16 20:14 被阅读0次

    小demo:客户端页面发送消息,服务端接受到原文回复过去。

    init.py

    app = None
    def create_app(config_name):
        global app
        app = Flask(__name__)
        #启动websocket server
        from . import socket_server
        socket_server.run()
        ....省略中间代码
        return app
    

    socket_server.py

    import json
    from flask_sockets import Sockets
    from app import app#从app根目录引用app=Flask(__name__)的实例
    
    sockets = Sockets(app)
    client_pool = []
    
    @sockets.route('/socket.io/echo')
    def echo_socket(ws):
        print('connent...')
    
        client_pool.append(ws)
        while not ws.closed:
            msg = ws.receive()
            print(f'recevice:{msg}')
            if msg is None:#一般是客户端关闭了
                print('msg is None')
                break
            msg = json.loads(msg)
            ws.send(f'reply : {msg["data"]}')
    
        client_pool.remove(ws)
    
    
    def thread_forever(name):
        #pip install flask_sockets
        from gevent import pywsgi
        from geventwebsocket.handler import WebSocketHandler
    
        try:
            #任意未使用端口
            server = pywsgi.WSGIServer(('0.0.0.0', 9000), app, handler_class=WebSocketHandler)
            print(app)
            print('web server start ... ')
            server.serve_forever()#这个会一直阻塞
            print('serve_forever end ... ')
        except Exception as e:
            print(f'server err: {e}')
            return None
    
    def run():
        # 为了防止create_app调用run这里被serve_forever阻塞,导致gunicorn超时杀掉进程
        _thread.start_new(thread_forever, ('server',))
    

    socket_client.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>websocket demo</title>
        <script src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script>
    </head>
    <body>
        <div id="content" style="width: 300px;height: 300px;border:1px solid #000;padding:6px;font-size: 12px;">
            
        </div>
        <br>
        <input id="say" type="input" name="say">
        <button onclick="sendtext()">发送</button>
    
        <script>
                //var ws = new WebSocket("ws://mp-api.hmyy.com:9000/socket.io/echo");  //本地测试,连接本地server
                var ws = new WebSocket("wss://mp-api.hmuu.com/socket.io/echo");  //生产环境,连接服务器server-ssl
                ws.onopen = function(event){
                    $("#content").append("web socket 已连接<br>");
                   console.log('web socket 已连接');
                    ws.send( JSON.stringify({'type':'open', 'data':{'name':'Jerry'}}));
                };
    
                ws.onmessage = function (event) {
                    $("#content").append(event.data+"<br>");
    
                };
                ws.onclose = function(){
                    $("#content").append("连接已关闭...<br>");
                }
    
                function sendtext(){
                    var txt = $('#say').val();
                    $('#say').val('');
                    console.log(txt);
                    ws.send( JSON.stringify({'type':'say', 'data':''+txt}));
                }
    
        </script>
        </body>
    </html>
    

    遇到的坑

    坑1 - nginx

    以上代码在本地runserver是没问题的。
    但是放到生成环境nginx+gunicorn就不行了。
    我们网站访问一般是80和443,所以nginx也就是监听这两个端口,然后转发给flask的web服务端口,如8000。
    并且服务器也不会放行其他端口到外网,所以我们客户端需要socket连接服务器到80/443端口,nginx接受到后,就转发给flask的socket监听端口,如9000.我们就还需要设置nginx配置,转发。
    nginx中加上配置

            location ^~/socket.io {
            proxy_pass_header Server;
            proxy_set_header Host $http_host;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_read_timeout 300s;
                proxy_pass http://127.0.0.1:9000;
            }
    

    所以我们的url路径ws://mp-api.hmyy.com:9000/socket.io/echo中会有socket-io字符串,只是为了nginx识别规则转发。(以上代码已经加上socket-io)

    坑2 - gunicorn

    gunicorn还会运行30秒就提示[CRITICAL] WORKER TIMEOUT,然后自动重启进程。


    image.png

    因为我们init.py在create_app()中执行的socket_server.run(),server.serve_forever()是一直阻塞的,所以create_app一直没有return,gunicorn就一直挂起了。
    解决:我们应该使用thread执行run()里面的代码
    以上代码我已经改成线程模式了。

    坑3 - cdn

    在本地测试环境,连接能一直保持存在。在生成环境10秒就断开,nginx的 proxy_read_timeout 600s;设置了10分钟,还是10秒断开。后来发现是腾讯cdn的问题,由于api域名使用了腾讯cdn,cdn主要由于缓存,websocket是动态内容,10秒没交互就会超时被断开,所以我们单独开了一个ws.xxx.com的子域名,用于ws连接,这样就没问题了。


    image.png

    取消cdn后,就是按照nginx设置都5分钟超时自动关闭了


    image.png

    相关文章

      网友评论

          本文标题:flask-sockets小demo在nginx+gunicor

          本文链接:https://www.haomeiwen.com/subject/mtqylltx.html