小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
网友评论