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

2025-06-15 12:45:02 3
  • 收藏
  • 管理
    .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;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:30px;margin-bottom:5px}.markdown-body h2{padding-bottom:12px;font-size:24px;border-bottom:1px solid #ececec}.markdown-body h3{font-size:18px;padding-bottom:0}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:15px}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1px solid #ddd;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size:.87em;padding:.065em .4em}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{text-decoration:none;color:#0269c8;border-bottom:1px solid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{color:#275b8c}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #f6f6f6}.markdown-body thead{background:#f6f6f6;color:#000;text-align:left}.markdown-body tr:nth-child(2n){background-color:#fcfcfc}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4px solid #cbcbcb;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}} Swoole教程栏目介绍如何查询Websocket的连接问题。 推荐:swoole教程问题 我们项目的 Websocket Server 使用的 Swoole,最近在搭建 beta 环境的时候发现 Websocket 协议虽然升级成功了,但是会出现定时重连,心跳、数据也一直没有发送。项目的生产环境和 beta 一致,但是生产环境确没有这个问题。 定位问题 为了方便调试 Swoole,以下测试是在本地环境下进行。 查看 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 1840x0000:450000ec00004000400600007f000001 E.....@.@.......0x0010:7f00000104bf d23f 9377304a6d2f9604.......?.w0Jm/..0x0020:801818d6 fee0 00000101080a76219272............v!.r 0x0030:76219271485454502f312e3120313031 v!.qHTTP/1.1.1010x0040:20537769746368696e672050726f746f.Switching.Proto0x0050:636f6c730d0a557067726164653a2077 cols..Upgrade:.w 0x0060:6562736f636b65740d0a436f6e6e6563 ebsocket..Connec0x0070:74696f6e3a20557067726164650d0a53 tion:.Upgrade..S 0x0080:65632d576562536f636b65742d416363 ec-WebSocket-Acc0x0090:6570743a2052637038516663446c3146 ept:.Rcp8QfcDl1F0x00a0:776e666a637738624933697171764551 wnfjcw8bI3iqqvEQ 0x00b0:3d0d0a5365632d576562536f636b6574=..Sec-WebSocket0x00c0:2d56657273696f6e3a2031330d0a5365-Version:.13..Se0x00d0:727665723a2073776f6f6c652d687474 rver:.swoole-htt 0x00e0:702d7365727665720d0a0d0a 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 00x0000:4500003400004000400600007f000001 E..4..@.@.......0x0010:7f000001 d23f 04bf6d2f960493773102.....?..m/...w1.0x0020:801018e8 fe28 00000101080a76219272.....(......v!.r 0x0030:76219272 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 00x0000:4500003400004000400600007f000001 E..4..@.@.......0x0010:7f00000104bf d23f 937731026d2f9604.......?.w1.m/..0x0020:801118d6 fe28 00000101080a76219273.....(......v!.s 0x0030:76219272 v!.r复制代码 追踪 Swoole 源码 我们现在知道了是 Swoole 主动断开了连接,但它是在什么时候断开的,又为什么要断开呢?就让我们从源码一探究竟。 从抓包结果看,发出响应报文到 close 连接的时间很短,所以猜测是握手阶段出了问题。从响应报文可以看出,Websocket 连接是建立成功的,推测 swoole_websocket_handshake() 的结果应该是 true,那么连接应该是在 swoole_websocket_handshake() 里 close 的。 复制// // swoole_websocket_server.ccint 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.ccbool 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.ccvoid 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){return0;}else{return1;}}else{/* HTTP/1.0 or earlier */if(parser->flags & F_CONNECTION_KEEP_ALIVE){return1;}else{return0;}}}复制代码 解决问题 从上面的结论我们可以知道,问题的关键点在于请求头的 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 终于正常了~
    上一页:学习微信小程序开发需要学习什么 下一页:学习哪种语言最容易找到工作?
    全部评论(0)