Flask SocketIO的正确配置

6
我一直在按照这个教程尝试使用nginx和gunicorn来运行Flask SocketIO。
nginx
server {
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_redirect off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /socket.io {
        proxy_pass http://localhost:8000/socket.io;
        proxy_redirect off;
        proxy_buffering off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

    }

}

gunicorn_config.py

bind = '127.0.0.1:8000'
workers = 2
worker_class = 'socketio.sgunicorn.GeventSocketIOWorker'

在Supervisor中,我使用以下代码调用我的应用程序:

[program:gunicorn-couponmonk]
directory = ~/couponmonk_project
command =~/venv/py2.7/bin/python    ~/venv/py2.7/bin/gunicorn -c ~/venv/py2.7/lib/python2.7/site-packages/gunicorn/gunicorn_config.py __init__.py 
stdout_logfile = /var/log/gunicorn/couponmonk-std.log
stderr_logfile = /var/log/gunicorn/couponmonk-err.log
user = mint

使用此配置,我的应用程序(不使用Flask SocketIO)可以正常工作。
我只是对如何使用socketIO感到困惑。如果我访问地址http://localhost:8000/socket.io,我会得到内部服务器错误的响应。
示例HTML/Javascripthttps://github.com/miguelgrinberg/Flask-SocketIO/blob/master/example/templates/index.html)包含以下行:
var socket = io.connect('http://' + document.domain + ':' + location.port + namespace);

我有两个问题:
1)这个变量应该指向哪个地址?
2)我的配置(nginx,gunicorn)是否正确?
抱歉如果这个问题很傻。我只是对这一切的工作方式感到困惑。
谢谢你的帮助。
**更新**
当我尝试访问http://localhost/socket.io时,这是nginx error.log文件的输出。
2015/06/20 14:05:08 [debug] 1917#0: *1 http cleanup add: 00000000009F2170
2015/06/20 14:05:08 [debug] 1917#0: *1 get rr peer, try: 1
2015/06/20 14:05:08 [debug] 1917#0: *1 socket 12
2015/06/20 14:05:08 [debug] 1917#0: *1 epoll add connection: fd:12 ev:80000005
2015/06/20 14:05:08 [debug] 1917#0: *1 connect to 127.0.0.1:8000, fd:12 #2
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream connect: -2
2015/06/20 14:05:08 [debug] 1917#0: *1 posix_memalign: 0000000000A14170:128 @16
2015/06/20 14:05:08 [debug] 1917#0: *1 event timer add: 12: 60000:1434773168437
2015/06/20 14:05:08 [debug] 1917#0: *1 http finalize request: -4, "/socket.io?" a:1, c:2
2015/06/20 14:05:08 [debug] 1917#0: *1 http request count:2 blk:0
2015/06/20 14:05:08 [debug] 1917#0: *1 post event 0000000000A295E0
2015/06/20 14:05:08 [debug] 1917#0: *1 post event 0000000000A29648
2015/06/20 14:05:08 [debug] 1917#0: *1 delete posted event 0000000000A29648
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream request: "/socket.io?"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream send request handler
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream send request
2015/06/20 14:05:08 [debug] 1917#0: *1 chain writer buf fl:1 s:439
2015/06/20 14:05:08 [debug] 1917#0: *1 chain writer in: 00000000009F21A8
2015/06/20 14:05:08 [debug] 1917#0: *1 writev: 439
2015/06/20 14:05:08 [debug] 1917#0: *1 chain writer out: 0000000000000000
2015/06/20 14:05:08 [debug] 1917#0: *1 event timer del: 12: 1434773168437
2015/06/20 14:05:08 [debug] 1917#0: *1 event timer add: 12: 60000:1434773168437
2015/06/20 14:05:08 [debug] 1917#0: *1 delete posted event 0000000000A295E0
2015/06/20 14:05:08 [debug] 1917#0: *1 http run request: "/socket.io?"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream check client, write event:1, "/socket.io"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream recv(): -1 (11: Resource temporarily unavailable)
2015/06/20 14:05:08 [debug] 1917#0: *1 post event 0000000000A15E38
2015/06/20 14:05:08 [debug] 1917#0: *1 post event 0000000000A29648
2015/06/20 14:05:08 [debug] 1917#0: *1 delete posted event 0000000000A29648
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream request: "/socket.io?"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream dummy handler
2015/06/20 14:05:08 [debug] 1917#0: *1 delete posted event 0000000000A15E38
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream request: "/socket.io?"
2015/06/20 14:05:08 [debug] 1917#0: *1 http upstream process header
2015/06/20 14:05:08 [debug] 1917#0: *1 malloc: 00000000009E88A0:4096
2015/06/20 14:05:08 [debug] 1917#0: *1 recv: fd:12 244 of 4096
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy status 500 "500 Internal Server Error"
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy header: "Connection: close"
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy header: "Content-Type: text/html"
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy header: "Content-Length: 141"
2015/06/20 14:05:08 [debug] 1917#0: *1 http proxy header done
2015/06/20 14:05:08 [debug] 1917#0: *1 xslt filter header
2015/06/20 14:05:08 [debug] 1917#0: *1 HTTP/1.1 500 Internal Server Error
Server: nginx/1.4.6 (Ubuntu)
Date: Sat, 20 Jun 2015 04:05:08 GMT
Content-Type: text/html
Content-Length: 141
Connection: keep-alive

不确定这有多大帮助,但我不知道在哪里寻找额外的信息。
我也很好奇为什么我可以访问这个:http://localhost/socket.io123abc 但是仍然会得到一个“内部服务器错误”,而不是“未找到”错误?
我还根据下面Miguel的答案更新了我的supervisord.conf文件。

supervisord.conf

[program:gunicorn-couponmonk]
directory = /home/giri/couponmonk_project
command = /home/giri/venv/py2.7/bin/python /home/giri/venv/py2.7/bin/gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker __init__:app 
stdout_logfile = /var/log/gunicorn/couponmonk-std.log
stderr_logfile = /var/log/gunicorn/couponmonk-err.log
user = mint

/var/log/gunicorn/couponmonk-err.log

2015-06-20 14:30:11 [3821] [ERROR] Connection in use: ('127.0.0.1', 8000)
2015-06-20 14:30:11 [3821] [ERROR] Retrying in 1 second.
2015-06-20 14:30:12 [3821] [ERROR] Connection in use: ('127.0.0.1', 8000)
2015-06-20 14:30:12 [3821] [ERROR] Retrying in 1 second.
2015-06-20 14:30:13 [3821] [ERROR] Connection in use: ('127.0.0.1', 8000)
2015-06-20 14:30:13 [3821] [ERROR] Retrying in 1 second.

这个列表会继续很长一段时间...


你可能在/var/log/gunicorn/couponmonk-*文件中有一些Python的回溯信息。你能找到它们并发布吗? - David K. Hess
@DavidK.Hess,我已经发布了输出。谢谢你的帮助。 - user2268507
1
看起来你不小心启动了同一个进程两次。仔细检查你的进程列表,确保只有一个gunicorn主进程在运行。 - David K. Hess
3个回答

2

我建议你让Flask-SocketIO在没有nginx和gunicorn的情况下工作。一旦你可以通过本地gevent服务器使其正常工作,就可以转移到实际设置。

关于你的问题:

1)这个变量应该指向哪个地址?

你的连接语句是正确的。Socket.IO将获取主机、端口和命名空间,并自行构建连接URL,包括 /socket.io 组件。你不需要在连接中指定它。

2)我的配置(nginx、gunicorn)是否正确?

我认为nginx配置是正确的。你似乎直接从我的文档复制过来了,我已经验证过它可以工作。

对于gunicorn配置,我不确定,因为你没有展示足够的项目信息。我使用的命令应该在你的supervisor配置文件中,如下所示:

gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker module:app

其中module是应用程序的主模块,app是Flask应用程序实例的名称。在使用SocketIO时,一定要使用单个worker,不要使用两个worker。


感谢你的帮助,Miguel。看起来“内部服务器错误”与我运行多个“gunicorn”实例有关。 - user2268507

1

如果有人正在阅读这篇旧文章,gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker module:app 命令已过时,正如Miguel在github flask-socketio issue中提到的那样。


1

首先,我要提到我不建议使用SocketIO。虽然它比WebSockets增加了一些有用的功能,但它使得真正的多个工作进程(水平扩展)负载均衡变得不可能,除非你让客户端粘着到单个工作进程或者使用像Redis这样的东西来共享状态信息。我建议看一下:

https://github.com/youen/gevent-websocket

原生WebSockets更加简单易用,可以跨多个前端工作进程进行真正的负载平衡。实现聊天室逻辑所需的代码非常少。
话虽如此,以下是您的问题的答案:
1)这个变量应该指向什么地址?
您的命名空间将是“”。 (根据Flask-SocketIO作者Miguel的说法,Javascript客户端会自动插入“socket.io”部分。)
因此,您的io.connect url应为“http://[hostname]/”。
2)我的配置(nginx,gunicorn)是否正确?
Ngnix配置似乎正确(但请参见下一节)。如果决定使用WebSockets,则可以考虑添加proxy_read_timeout 3600;。否则,除非您有一个喋喋不休的协议,否则WebSocket将在一分钟后被Nginx丢弃(默认值)。 (此外,根据Miguel的说法,SocketIO具有处理此问题的心跳。)
Gunicorn配置不正确。使用SocketIO有几种选择:
  1. 在gunicorn中设置workers = 1,以便每个SocketIO客户端都与同一个工作进程通信。
  2. 更改您的nginx配置以使用ip-hash命令,这将导致客户端根据客户端IP地址被分配到工作进程。
  3. 使用Redis或其他数据库允许每个工作进程共享状态信息(尽管目前还没有人为Gevent SocketIO实现该功能)。

这些选择是被迫的,因为SocketIO使用了一种有状态的设置机制,当您尝试进行水平扩展时会出现问题。请参见此问题以获取更多信息: https://github.com/abourget/gevent-socketio/issues/112

这里是SocketIO文档的链接,也讨论了它:http://socket.io/docs/using-multiple-nodes/

如果您遇到Internal Server Error,那么很可能有异常被记录在某个地方。尝试找到它并将其添加到您的问题中。

请注意,您不能通过在浏览器地址栏中输入该URL来测试此功能-浏览器默认情况下不会使用正确的WebSocket协议,并且对您没有用处。必须使用Javascript API设置WebSocket连接。
另外,尝试使用端口号访问该URL将绕过nginx-这可能不是您想要做的。 Nginx通常侦听80/443并将请求转发到localhost:8000(这称为“反向代理”设置)。

2
你的回答有很多不准确之处。Socket.IO 除了 WebSockets,还可以通过常规 HTTP 进行工作,并为每个客户端选择最佳传输方式。它还可以跟踪连接的用户,并允许服务器向所有用户或进入房间的用户组进行广播。WebSocket 没有这些功能。原生 WebSocket 更简单、更易用,并且可以在多个前端工作程序之间进行扩展,而无需部署像 Redis 这样的东西。当然,这不是因为 WebSocket,而是因为没有广播/房间。 - Miguel Grinberg
由于您在nginx配置中将SocketIO流量分隔为单独的位置,因此您的名称空间定义为/socket.io。这是不正确的,/socket.io资源名称是由Socket.IO客户端添加的,它不会出现在连接URL中。否则,除非您使用了聊天协议,否则底层WebSocket将每分钟被Nginx丢弃(默认值)。Socket.IO中的心跳数据包将防止这种情况发生。 - Miguel Grinberg
同样的事情也适用于Socket.IO。Redis和gevent-socketio之间绝对没有任何联系。 - Miguel Grinberg
这里有一个微妙之处,我认为你可能会忽略 - 我的观点与已建立的会话无关 - 它与会话建立有关。SocketIO协议具有两个步骤的设置过程。如果第一个http请求发送到一个工作进程,而第二个http请求发送到另一个工作进程,则SocketIO连接将无法建立。它需要像Redis这样的东西来在步骤1和步骤2之间共享状态信息,以使真正的负载平衡工作。 - David K. Hess
SocketIO的官方文档提到客户端必须是sticky的:http://socket.io/docs/using-multiple-nodes/ 这就是为什么你必须要有一个worker,使用nginx ip-hash或者使用共享存储来共享socket会话信息。 - David K. Hess
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接