學習在Swoole原始碼中查詢 Websocket 的連線問題
Swoole教學欄位介紹如何查詢Websocket的連線問題。
#推薦:swoole教學
#問題
我們專案的Websocket伺服器的Swoole,最近在搭建測試環境的時候發現Websocket協議雖然升級成功了,但是使用會出現定時重連,心跳、數據也一直沒有發送。項目的生產環境和測試環境一致,但是生產環境確實沒有這個問題。
定位問題
為了方便調試Swoole,以下測試是在本地環境下進行。
查看PHP日誌
在PHP 日誌裡,發現一條錯誤日誌: ErrorException: Swoole\WebSocket\Server::push(): theconnected client of connection[47] is not a websocket client or close
,說明Websocket連線已經關閉了。
抓包
既然連線被關掉掉了,那我們來看看是誰主動關閉的連線。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_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 的連線問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

隨著網路科技的不斷發展,即時通訊已經成為了日常生活中不可或缺的一部分。利用WebSockets技術可以實現高效、低延遲的即時通信,而PHP作為互聯網領域使用最廣泛的開發語言之一,也提供了相應的WebSocket支援。本文將為大家介紹如何使用PHP和WebSocket實現即時通信,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單

PHP和WebSocket:實現即時資料傳輸的最佳實踐方法引言:在Web應用程式開發中,即時資料傳輸是一項非常重要的技術需求。傳統的HTTP協定是一種請求-回應模式的協議,不能有效地實現即時資料傳輸。為了滿足即時資料傳輸的需求,WebSocket協定應運而生。 WebSocket是一種全雙工通訊協議,它提供了一種在單一TCP連接上進行全雙工通訊的方式。相比於H

如何利用Java和WebSocket實現即時股票行情推播引言:隨著網路的快速發展,股票行情即時推播成為了投資人關注的焦點之一。傳統的股票行情推送方式存在延遲較高、刷新速度慢等問題,對於投資人來說,無法及時獲得最新的股票行情資訊可能會導致投資決策的誤差。而基於Java和WebSocket的即時股票行情推送可以有效解決這個問題,使投資者能夠第一時間獲取到最新的

Laravel 中使用 Swoole 協程可以並發處理大量請求,優點包括:同時處理:允許同時處理多個請求。高效能:基於 Linux epoll 事件機制,高效處理請求。低資源消耗:所需伺服器資源更少。易於整合:與 Laravel 框架無縫集成,使用簡單。

JavaWebsocket如何實現線上白板功能?在現代網路時代,人們越來越注重即時協作和互動的體驗。線上白板就是一種基於Websocket實現的功能,它能夠使多個使用者即時協作編輯同一個畫板,完成繪圖和標註等操作,為線上教育、遠端會議、團隊協作等場景提供了便捷的解決方案。一、技術背景WebSocket是HTML5提供的一種新的協議,它在同一條TCP連接上實

在本文中,我們將比較伺服器發送事件(SSE)和 WebSocket,兩者都是用於傳遞資料的可靠方法。我們將在八個方面對它們進行分析,包括通訊方向、底層協定、安全性、易用性、效能、訊息結構、易用性和測試工具。這些方面的比較總結如下:類別伺服器發送事件(SSE)WebSocket通訊方向單向雙向底層協定HTTPWebSocket 協定安全性與HTTP 相同存在安全漏洞易用性設定簡單設定複雜效能訊息傳送速度快受訊息處理和連線管理影響訊息結構純文字文字或二進位易用性廣泛可用對WebSocket 整合有

Golang是一種功能強大的程式語言,它在WebSocket程式設計中的使用越來越受到開發者的重視。 WebSocket是一種基於TCP的協議,它允許在客戶端和伺服器之間進行雙向通訊。在本文中,我們將介紹如何使用Golang編寫高效的WebSocket伺服器,同時處理多個並發連線。在介紹技巧前,我們先來學習什麼是WebSocket。 WebSocket簡介Web

如何在golang中使用WebSocket進行文件傳輸WebSocket是一種支援雙向通訊的網路協議,能夠在瀏覽器和伺服器之間建立持久的連線。在golang中,我們可以使用第三方函式庫gorilla/websocket來實作WebSocket功能。本文將介紹如何使用golang和gorilla/websocket函式庫來進行檔案傳輸。首先,我們需要安裝gorilla
