Swoole源码中如何查询Websocket的连接问题详解
问题
我们项目的WebsocketServer使用的Swoole,最近在搭建beta环境的时候发现Websocket协议虽然升级成功了,但是会出现定时重连,心跳、数据也一直没有发送。项目的生产环境和beta一致,但是生产环境确没有这个问题。
定位问题
为了方便调试Swoole,以下测试是在本地环境下进行。
查看PHP日志
在PHP日志里,发现一条错误日志:ErrorException:Swoole\WebSocket\Server::push():theconnectedclientofconnection[47]isnotawebsocketclientorclosed,说明Websocket连接已经close了。
抓包
既然连接被close掉了,那我们来看看是谁主动关闭的连接。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],length184
0x0000: 450000ec00004000400600007f000001 E.....@.@.......
0x0010: 7f00000104bfd23f9377304a6d2f9604 .......?.w0Jm/..
0x0020: 801818d6fee000000101080a76219272 ............v!.r
0x0030: 76219271485454502f312e3120313031 v!.qHTTP/1.1.101
0x0040: 20537769746368696e672050726f746f .Switching.Proto
0x0050: 636f6c730d0a557067726164653a2077 cols..Upgrade:.w
0x0060: 6562736f636b65740d0a436f6e6e6563 ebsocket..Connec
0x0070: 74696f6e3a20557067726164650d0a53 tion:.Upgrade..S
0x0080: 65632d576562536f636b65742d416363 ec-WebSocket-Acc
0x0090: 6570743a2052637038516663446c3146 ept:.Rcp8QfcDl1F
0x00a0: 776e666a637738624933697171764551 wnfjcw8bI3iqqvEQ
0x00b0: 3d0d0a5365632d576562536f636b6574 =..Sec-WebSocket
0x00c0: 2d56657273696f6e3a2031330d0a5365 -Version:.13..Se
0x00d0: 727665723a2073776f6f6c652d687474 rver:.swoole-htt
0x00e0: 702d7365727665720d0a0d0a p-server....
10:22:58.060906IP127.0.0.1.53823>127.0.0.1.1215:Flags[.],ack185,win6376,options[nop,nop,TSval1981911666ecr1981911666],length0
0x0000: 4500003400004000400600007f000001 E..4..@.@.......
0x0010: 7f000001d23f04bf6d2f960493773102 .....?..m/...w1.
0x0020: 801018e8fe2800000101080a76219272 .....(......v!.r
0x0030: 76219272 v!.r
10:22:58.061467IP127.0.0.1.1215>127.0.0.1.53823:Flags[F.],seq185,ack1372,win6358,options[nop,nop,TSval1981911667ecr1981911666],length0
0x0000: 4500003400004000400600007f000001 E..4..@.@.......
0x0010: 7f00000104bfd23f937731026d2f9604 .......?.w1.m/..
0x0020: 801118d6fe2800000101080a76219273 .....(......v!.s
0x0030: 76219272 v!.r
追踪Swoole源码
我们现在知道了是Swoole主动断开了连接,但它是在什么时候断开的,又为什么要断开呢?就让我们从源码一探究竟。
从抓包结果看,发出响应报文到close连接的时间很短,所以猜测是握手阶段出了问题。从响应报文可以看出,Websocket连接是建立成功的,推测swoole_websocket_handshake()的结果应该是true,那么连接应该是在swoole_websocket_handshake()里close的。
////swoole_websocket_server.cc
intswoole_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()里,前面部分都是设置响应的header,响应报文则是在swoole_http_response_end()里发出的,它的结果也就是swoole_websocket_handshake的结果。
//swoole_websocket_server.cc
boolswoole_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;
zvalretval;
swoole_http_response_end(ctx,nullptr,&retval);
returnZ_TYPE(retval)==IS_TRUE;
}
从swoole_http_response_end()代码中我们发现,如果ctx->keepalive为0的话则关闭连接,断点调试下发现还真就是0。至此,连接断开的地方我们就找到了,下面我们就看下什么情况下ctx->keepalive设置为1。
//swoole_http_response.cc
voidswoole_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{
/*connectionshouldbeclosedwhenhandshakefailed*/
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协议。
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{
return1;
}
}else{
/*HTTP/1.0orearlier*/
if(parser->flags&F_CONNECTION_KEEP_ALIVE){
return1;
}else{
return0;
}
}
}
解决问题
从上面的结论我们可以知道,问题的关键点在于请求头的Connection和HTTP协议版本。
后来问了下运维,生产环境的LB会在转发请求时,会将HTTP协议版本修改为1.1,这也是为什么只有beta环境存在这个问题,nginx的access_log也印证了这一点。
那么解决这个问题就很简单了,就是手动升级下HTTP协议的版本,完整的nginx配置如下。
upstreamservice{
server127.0.0.1:1215;
}
server{
listen80;
server_namedev-service.ts.com;
location/{
proxy_set_headerHost$http_host;
proxy_set_headerScheme$scheme;
proxy_set_headerSERVER_PORT$server_port;
proxy_set_headerREMOTE_ADDR$remote_addr;
proxy_set_headerX-Forwarded-For$proxy_add_x_forwarded_for;
proxy_set_headerUpgrade$http_upgrade;
proxy_set_headerConnection$connection_upgrade;
proxy_http_version1.1;
proxy_passhttp://service;
}
}
重启Nginx后,Websocket终于正常了~
总结
到此这篇关于Swoole源码中如何查询Websocket的连接问题的文章就介绍到这了,更多相关Swoole源码查询Websocket连接问题内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。