热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

小编给大家分享一下Swoole源码中怎么查询Websocket的连接问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这

小编给大家分享一下Swoole源码中怎么查询Websocket的连接问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

问题

我们项目的 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 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 终于正常了~

以上是“Swoole源码中怎么查询Websocket的连接问题”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程笔记行业资讯频道!


推荐阅读
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
author-avatar
手机用户2602915211
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有