当前位置: 首页 > 后端技术 > PHP

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

时间:2023-03-29 20:27:47 PHP

问题我们项目的WebsocketServer使用的是Swoole。最近在搭建内测环境的时候,发现虽然Websocket协议升级成功,但是会出现定时重连、心跳、数据。发送。项目的生产环境和beta是一致的,但是生产环境没有这个问题。定位问题为了方便Swoole的调试,下面在本地环境下进行测试。查看PHP日志在PHP日志中,发现了一条错误日志:ErrorException:Swoole\WebSocket\Server::push():theconnectedclientofconnection[47]isnotawebsocketclientorclosed,说明Websocket连接有被关闭。抓包既然关闭了连接,我们看看是谁主动关闭了连接。Swoole监听的端口是1215,通过tcpdump-nnilo0-Xport1215可以看出,Swoole在发送协议升级响应报文后,发送了一个Fin报文段,即Swoole主动断开连接,所以会出现浏览器显示WebSocket连接建立成功,但是周期性重连的问题。10:22:58.060810IP127.0.0.1.1215>127.0.0.1.53823:Flags[P.],seq1:185,ack1372,win6358,options[nop,nop,TSval1981911666ecr1981911665],长度1840x0000:450000ec00004000400600007f000001E.....@.@......0x0010:7f00000104bfd23f9377304a6d2f9604......?.w0Jm/..0x0020:801818d6fee000000101080a76219272..................v!.r0x0030:76219271485454502f312e3120313031v!.qHTTP/1.1.1010x0040:2053467666e2050726f746f.switching.proto0x0050:636f6C730D0A5570677267726164653A2077COLS..upgrade:.w0x0060:.w0x0060:65627362736f636b636b65740d0a436f6563e6563e6563Ebocket656e6563EbocketET..Upgrade..S0x0080:65632d576562536f636b65742d416363ec-WebSocket-Acc0x0090:6570743a2052637038516663446c3146ept:.Rcp8QfcDl1F0x00a0:776e666a637738624933697171764551wnfjcw8bI3iqqvEQ0x00b0:3d0d0a5365632个d576562536f636b6574=..Sec-WebSocket0x00c0:2d56657273696f6e3a2031330d0a5365-Version:.13..Se0x00d0:727665723a2073776f6f6c652d687474rver:.swoole-htt0x00e0:702d7365727665720d0a0d0ap-server....10:22:58.060906IP127.0.0.1.53823>127.0.0.1.1215:Flags[.],ack185,win6376,options[nop,nop,TSval1981911666ecr1981911666],长度00x0000:4500003400004000400600007f000001E..4..@.@......0x0010:7f000001d23f04bf6d2f960493773102....?..m/...w1。0x0020:801018e8fe2800000101080a76219272.....(......v!.r0x0030:76219272v!.r10:22:58.061467IP127.0.0.1.1215>127.0.0.1.53823:标志[F.],seq185,ack1372,win6358,选项[nop,nop,TSval1981911667ecr1981911666],长度00x0000:4500003400004000400600007f000001E..4..@.@.......0x0010:7f00000104bfd23f937731026d2f9604.......?.w1.m/..0x0020:801118d6fe2800000101080a76219273.....(.....v!.s0x0030:76219272v!.r跟踪Swoole源码我们现在知道Swoole主动断开了,但是什么时候断开的disconnect,为什么会断开呢,让我们从源码中找出来,从抓包结果来看,从发送响应报文到关闭连接的时间很短,所以猜测是握手阶段出了问题吧从响应信息可以看出Websocket连接建立成功,推测swoole_websocket_handshake()的结果应该为true,那么应该在swoole_websocket_handshake()中关闭连接////swoole_websocket_server.ccintswoole_websocket_onHandshake(swServer*serv,swListenPort*port,http_context*ctx){intfd=ctx->fd;boolsuccess=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);}returnSW_OK;}跟踪到swoole_websocket_handshake()中,第一部分是设置响应头,响应报文是在swoole_http_response_end()中发送的,它的结果也是swoole_websocket_handshake的结果。//swoole_websocket_server.ccboolswoole_websocket_handshake(http_context*ctx){...swoole_http_response_set_header(ctx,ZEND_STRL("升级"),ZEND_STRL("websocket"),false);swoole_http_response_set_header(ctx,ZEND_STRL,ZEND_STRL)(升级"),false);swoole_http_response_set_header(ctx,ZEND_STRL("Sec-WebSocket-Accept"),sec_buf,sec_l??en,false);swoole_http_response_set_header(ctx,ZEND_STRL("Sec-WebSocket-Version"),ZEND_STRL();...ctx->response.status=101;ctx->upgrade=1;zvalretval;swoole_http_response_end(ctx,nullptr,&retval);返回Z_TYPE(retval)==IS_TRUE;}来自swoole_http_response_end()代码我们发现如果ctx->keepalive为0,则关闭连接,断点调试下发现为0,至此,我们找到了断开连接的地方,接下来,我们会看到在什么情况下ctx->keepalive设置为1。//swoole_http_response.ccvoidswoole_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;返回假;}}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_status=WEBSOCKIV}else{/*握手失败时应该关闭连接*/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协议规定请求头中的Connection必须设置为Upgrade,所以我们需要改用HTTP/1.1协议。intswoole_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{返回1;}}else{/*HTTP/1.0或更早版本*/if(parser->flags&F_CONNECTION_KEEP_ALIVE){return1;}否则{返回0;}}}解决问题从上面的结论,我们可以知道问题的关键点在于请求头的Connection和HTTP协议版本。后来问了运维。生产环境中的LB在转发请求时会修改HTTP协议版本为1.1。这也是为什么只有beta环境才有这个问题。nginx的access_log也证实了这一点。那么解决这个问题就很简单了,就是手动升级HTTP协议的版本。完整的nginx配置如下。上游服务{服务器127.0.0.1:1215;}服务器{听80;服务器名称dev-service.ts.com;位置/{proxy_set_header主机$http_host;proxy_set_header计划$scheme;proxy_set_headerSERVER_PORT$server_port;proxy_set_headerREMOTEX_ADDR$remote_add-Forwarded-For$proxy_add_x_forwarded_for;proxy_set_header升级$http_upgrade;proxy_set_header连接$connection_upgrade;proxy_http_version1.1;proxy_passhttp://服务;优秀的PHP架构师教程目录,只要你看得懂,保证你的薪水更上一层楼(持续更新中)。以上内容希望对大家有所帮助。很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写的太多了。没有方向感,不知道从哪里开始提高。我整理了一些资料,包括但不限于:分布式架构、高扩展性、高性能、高并发、服务器性能调优、TP6、laravel、YII2、Redis、Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等知识点,需要进阶干货的可以免费分享给你。有需要的可以点击下方链接领取高级PHP月薪30k>>>架构师成长之路【免费获取视频和面试资料】

最新推荐
猜你喜欢