我們項(xiàng)目的 Websocket Server 使用的 Swoole,最近在搭建 beta 環(huán)境的時(shí)候發(fā)現(xiàn) Websocket 協(xié)議雖然升級成功了,但是會出現(xiàn)定時(shí)重連,心跳、數(shù)據(jù)也一直沒有發(fā)送。項(xiàng)目的生產(chǎn)環(huán)境和 beta 一致,但是生產(chǎn)環(huán)境確沒有這個(gè)問題。
定位問題查看 PHP 日志為了方便調(diào)試 Swoole,以下測試是在本地環(huán)境下進(jìn)行。
在 PHP 日志里,發(fā)現(xiàn)一條錯誤日志: ErrorException: Swoole\\WebSocket\\Server::push(): the connected client of connection[47] is not a websocket client or closed
,說明 Websocket 連接已經(jīng) close 了。
既然連接被 close 掉了,那我們來看看是誰主動關(guān)閉的連接。Swoole 監(jiān)聽的端口是 1215,通過 tcpdump -nni lo0 -X port 1215
可以看到,Swoole 在發(fā)出協(xié)議升級的響應(yīng)報(bào)文后,又發(fā)出了 Fin 報(bào)文段,即 Swoole 主動斷開了連接,所以才會出現(xiàn)瀏覽器顯示 WebSocket 連接建立成功,但是又定時(shí)重連的問題。
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復(fù)制代碼追蹤 Swoole 源碼
我們現(xiàn)在知道了是 Swoole 主動斷開了連接,但它是在什么時(shí)候斷開的,又為什么要斷開呢?就讓我們從源碼一探究竟。
從抓包結(jié)果看,發(fā)出響應(yīng)報(bào)文到 close 連接的時(shí)間很短,所以猜測是握手階段出了問題。從響應(yīng)報(bào)文可以看出,Websocket 連接是建立成功的,推測 swoole_websocket_handshake()
的結(jié)果應(yīng)該是 true
,那么連接應(yīng)該是在 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; }復(fù)制代碼
追蹤進(jìn) swoole_websocket_handshake()
里,前面部分都是設(shè)置響應(yīng)的 header,響應(yīng)報(bào)文則是在 swoole_http_response_end()
里發(fā)出的,它的結(jié)果也就是 swoole_websocket_handshake
的結(jié)果。
// 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; }復(fù)制代碼
從 swoole_http_response_end()
代碼中我們發(fā)現(xiàn),如果 ctx->keepalive
為 0 的話則關(guān)閉連接,斷點(diǎn)調(diào)試下發(fā)現(xiàn)還真就是 0。至此,連接斷開的地方我們就找到了,下面我們就看下什么情況下 ctx->keepalive
設(shè)置為 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); // 此時(shí)websocket_statue 已經(jīng)是WEBSOCKET_STATUS_ACTIVE,不會走進(jìn)這步邏輯 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; }復(fù)制代碼
最終我們找到 ctx->keepalive
是在 swoole_http_should_keep_alive()
里設(shè)置的。從代碼我們知道,當(dāng) HTTP 協(xié)議是 1.1 版本時(shí),keepalive 取決于 header 沒有設(shè)置 Connection: close
;當(dāng)為 1.0 版本時(shí),header 需設(shè)置 Connection: keep-alive
。
Websocket 協(xié)議規(guī)定,請求 header 里的 Connection 需設(shè)置為 Upgrade
,所以我們需要改用 HTTP/1.1 協(xié)議。
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; } } }復(fù)制代碼解決問題
從上面的結(jié)論我們可以知道,問題的關(guān)鍵點(diǎn)在于請求頭的 Connection 和 HTTP 協(xié)議版本。
后來問了下運(yùn)維,生產(chǎn)環(huán)境的 LB 會在轉(zhuǎn)發(fā)請求時(shí),會將 HTTP 協(xié)議版本修改為 1.1,這也是為什么只有 beta 環(huán)境存在這個(gè)問題,nginx 的 access_log 也印證了這一點(diǎn)。
那么解決這個(gè)問題就很簡單了,就是手動升級下 HTTP 協(xié)議的版本,完整的 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; } }復(fù)制代碼
重啟 Nginx 后,Websocket 終于正常了~
網(wǎng)站欄目:學(xué)習(xí)在Swoole源碼中查詢Websocket的連接問題
網(wǎng)站網(wǎng)址:http://www.rwnh.cn/article32/cjddpc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動網(wǎng)站建設(shè)、定制網(wǎng)站、自適應(yīng)網(wǎng)站、外貿(mào)建站、商城網(wǎng)站、域名注冊
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)