最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501
当前位置: 首页 - 科技 - 知识百科 - 正文

学习在Swoole源码中查询 Websocket 的连接问题

来源:懂视网 责编:小采 时间:2020-11-27 13:59:49
文档

学习在Swoole源码中查询 Websocket 的连接问题

学习在Swoole源码中查询 Websocket 的连接问题:.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1.markdown-body h2.markdown-body h3.markdown-body h4.markdown-body h5.markdown-body h6{line-height:1.5;ma
推荐度:
导读学习在Swoole源码中查询 Websocket 的连接问题:.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1.markdown-body h2.markdown-body h3.markdown-body h4.markdown-body h5.markdown-body h6{line-height:1.5;ma

查看 PHP 日志

在 PHP 日志里,发现一条错误日志: ErrorException: Swoole\WebSocket\Server::push(): the connected client of connection[47] is not a websocket client or closed,说明 Websocket 连接已经 close 了。

抓包

既然连接被 close 掉了,那我们来看看是谁主动关闭的连接。Swoole 监听的端口是 1215,通过 tcpdump -nni lo0 -X port 1215 可以看到,Swoole 在发出协议升级的响应报文后,又发出了 Fin 报文段,即 Swoole 主动断开了连接,所以才会出现浏览器显示 WebSocket 连接建立成功,但是又定时重连的问题。

10:22:58.060810 IP 127.0.0.1.1215 > 127.0.0.1.53823: Flags [P.], seq 1:185, ack 1372, win 6358, options [nop,nop,TS val 1981911666 ecr 1981911665], length 184
 0x0000: 4500 00ec 0000 4000 4006 0000 7f00 0001 E.....@.@.......
 0x0010: 7f00 0001 04bf d23f 9377 304a 6d2f 9604 .......?.w0Jm/..
 0x0020: 8018 18d6 fee0 0000 0101 080a 7621 9272 ............v!.r
 0x0030: 7621 9271 4854 5450 2f31 2e31 2031 3031 v!.qHTTP/1.1.101
 0x0040: 2053 7769 7463 6869 6e67 2050 726f 746f .Switching.Proto
 0x0050: 636f 6c73 0d0a 5570 6772 6164 653a 2077 cols..Upgrade:.w
 0x0060: 6562 736f 636b 6574 0d0a 436f 6e6e 6563 ebsocket..Connec
 0x0070: 7469 6f6e 3a20 5570 6772 6164 650d 0a53 tion:.Upgrade..S
 0x0080: 6563 2d57 6562 536f 636b 6574 2d41 6363 ec-WebSocket-Acc
 0x0090: 6570 743a 2052 6370 3851 6663 446c 3146 ept:.Rcp8QfcDl1F
 0x00a0: 776e 666a 6377 3862 4933 6971 7176 4551 wnfjcw8bI3iqqvEQ
 0x00b0: 3d0d 0a53 6563 2d57 6562 536f 636b 6574 =..Sec-WebSocket
 0x00c0: 2d56 6572 7369 6f6e 3a20 3133 0d0a 5365 -Version:.13..Se
 0x00d0: 7276 6572 3a20 7377 6f6f 6c65 2d68 7474 rver:.swoole-htt
 0x00e0: 702d 7365 7276 6572 0d0a 0d0a p-server....
10:22:58.060906 IP 127.0.0.1.53823 > 127.0.0.1.1215: Flags [.], ack 185, win 6376, options [nop,nop,TS val 1981911666 ecr 1981911666], length 0
 0x0000: 4500 0034 0000 4000 4006 0000 7f00 0001 E..4..@.@.......
 0x0010: 7f00 0001 d23f 04bf 6d2f 9604 9377 3102 .....?..m/...w1.
 0x0020: 8010 18e8 fe28 0000 0101 080a 7621 9272 .....(......v!.r
 0x0030: 7621 9272 v!.r
10:22:58.061467 IP 127.0.0.1.1215 > 127.0.0.1.53823: Flags [F.], seq 185, ack 1372, win 6358, options [nop,nop,TS val 1981911667 ecr 1981911666], length 0
 0x0000: 4500 0034 0000 4000 4006 0000 7f00 0001 E..4..@.@.......
 0x0010: 7f00 0001 04bf d23f 9377 3102 6d2f 9604 .......?.w1.m/..
 0x0020: 8011 18d6 fe28 0000 0101 080a 7621 9273 .....(......v!.s
 0x0030: 7621 9272 v!.r复制代码

追踪 Swoole 源码

我们现在知道了是 Swoole 主动断开了连接,但它是在什么时候断开的,又为什么要断开呢?就让我们从源码一探究竟。

从抓包结果看,发出响应报文到 close 连接的时间很短,所以猜测是握手阶段出了问题。从响应报文可以看出,Websocket 连接是建立成功的,推测 swoole_websocket_handshake() 的结果应该是 true,那么连接应该是在 swoole_websocket_handshake() 里 close 的。

// // swoole_websocket_server.cc
int swoole_websocket_onHandshake(swServer *serv, swListenPort *port, http_context *ctx)
{
 int fd = ctx->fd;
 bool success = swoole_websocket_handshake(ctx);
 if (success)
 {
 swoole_websocket_onOpen(serv, ctx);
 }
 else
 {
 serv->close(serv, fd, 1);
 }
 if (!ctx->end)
 {
 swoole_http_context_free(ctx);
 }
 return SW_OK;
}复制代码

追踪进 swoole_websocket_handshake() 里,前面部分都是设置响应的 header,响应报文则是在 swoole_http_response_end() 里发出的,它的结果也就是 swoole_websocket_handshake 的结果。

// swoole_websocket_server.cc
bool swoole_websocket_handshake(http_context *ctx)
{
 ...

 swoole_http_response_set_header(ctx, ZEND_STRL("Upgrade"), ZEND_STRL("websocket"), false);
 swoole_http_response_set_header(ctx, ZEND_STRL("Connection"), ZEND_STRL("Upgrade"), false);
 swoole_http_response_set_header(ctx, ZEND_STRL("Sec-WebSocket-Accept"), sec_buf, sec_len, false);
 swoole_http_response_set_header(ctx, ZEND_STRL("Sec-WebSocket-Version"), ZEND_STRL(SW_WEBSOCKET_VERSION), false);

 ...

 ctx->response.status = 101;
 ctx->upgrade = 1;

 zval retval;
 swoole_http_response_end(ctx, nullptr, &retval);
 return Z_TYPE(retval) == IS_TRUE;
}复制代码

swoole_http_response_end() 代码中我们发现,如果 ctx->keepalive 为 0 的话则关闭连接,断点调试下发现还真就是 0。至此,连接断开的地方我们就找到了,下面我们就看下什么情况下 ctx->keepalive 设置为 1。

// swoole_http_response.cc
void swoole_http_response_end(http_context *ctx, zval *zdata, zval *return_value)
{
 if (ctx->chunk) {
 ...
 } else {
 ...

 if (!ctx->send(ctx, swoole_http_buffer->str, swoole_http_buffer->length))
 {
 ctx->send_header = 0;
 RETURN_FALSE;
 } 
 }

 if (ctx->upgrade && !ctx->co_socket) {
 swServer *serv = (swServer*) ctx->private_data;
 swConnection *conn = swWorker_get_connection(serv, ctx->fd);

 // 此时websocket_statue 已经是WEBSOCKET_STATUS_ACTIVE,不会走进这步逻辑
 if (conn && conn->websocket_status == WEBSOCKET_STATUS_HANDSHAKE) {
 if (ctx->response.status == 101) {
 conn->websocket_status = WEBSOCKET_STATUS_ACTIVE;
 } else {
 /* connection should be closed when handshake failed */
 conn->websocket_status = WEBSOCKET_STATUS_NONE;
 ctx->keepalive = 0;
 }
 }
 }

 if (!ctx->keepalive) {
 ctx->close(ctx);
 }
 ctx->end = 1;
 RETURN_TRUE;
}复制代码

最终我们找到 ctx->keepalive 是在 swoole_http_should_keep_alive() 里设置的。从代码我们知道,当 HTTP 协议是 1.1 版本时,keepalive 取决于 header 没有设置 Connection: close;当为 1.0 版本时,header 需设置 Connection: keep-alive

Websocket 协议规定,请求 header 里的 Connection 需设置为 Upgrade,所以我们需要改用 HTTP/1.1 协议。

int swoole_http_should_keep_alive (swoole_http_parser *parser)
{
 if (parser->http_major > 0 && parser->http_minor > 0) {
 /* HTTP/1.1 */
 if (parser->flags & F_CONNECTION_CLOSE) {
 return 0;
 } else {
 return 1;
 }
 } else {
 /* HTTP/1.0 or earlier */
 if (parser->flags & F_CONNECTION_KEEP_ALIVE) {
 return 1;
 } else {
 return 0;
 }
 }
}复制代码

解决问题

从上面的结论我们可以知道,问题的关键点在于请求头的 Connection 和 HTTP 协议版本。

后来问了下运维,生产环境的 LB 会在转发请求时,会将 HTTP 协议版本修改为 1.1,这也是为什么只有 beta 环境存在这个问题,nginx 的 access_log 也印证了这一点。

那么解决这个问题就很简单了,就是手动升级下 HTTP 协议的版本,完整的 nginx 配置如下。

upstream service {
 server 127.0.0.1:1215;
}

server {
 listen 80;
 server_name dev-service.ts.com;

 location / {
 proxy_set_header Host $http_host;
 proxy_set_header Scheme $scheme;
 proxy_set_header SERVER_PORT $server_port;
 proxy_set_header REMOTE_ADDR $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header Upgrade $http_upgrade;
 proxy_set_header Connection $connection_upgrade;
 proxy_http_version 1.1;

 proxy_pass http://service;
 }
}复制代码

重启 Nginx 后,Websocket 终于正常了~

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

文档

学习在Swoole源码中查询 Websocket 的连接问题

学习在Swoole源码中查询 Websocket 的连接问题:.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1.markdown-body h2.markdown-body h3.markdown-body h4.markdown-body h5.markdown-body h6{line-height:1.5;ma
推荐度:
标签: 学习 WebSocket swoole
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top