Nginx支援WebSocket反向代理-學習小結

散盡浮華發表於2018-08-21

 

WebSocket是目前比較成熟的技術了,WebSocket協議為建立客戶端和伺服器端需要實時雙向通訊的webapp提供了一個選擇。其為HTML5的一部分,WebSocket相較於原來開發這類app的方法來說,其能使開發更加地簡單。大部分現在的瀏覽器都支援WebSocket,比如Firefox,IE,Chrome,Safari,Opera,並且越來越多的伺服器框架現在也同樣支援WebSocket。

在實際的生產環境中,要求多個WebSocket伺服器必須具有高效能和高可用,那麼WebSocket協議就需要一個負載均衡層,NGINX從1.3版本開始支援WebSocket,其可以作為一個反向代理和為WebSocket程式做負載均衡。

WebSocket協議與HTTP協議不同,但WebSocket握手與HTTP相容,使用HTTP升級工具將連線從HTTP升級到WebSocket。這允許WebSocket應用程式更容易地適應現有的基礎架構。例如,WebSocket應用程式可以使用標準HTTP埠80和443,從而允許使用現有的防火牆規則。

WebSocket應用程式可以在客戶端和伺服器之間保持長時間執行的連線,從而有助於開發實時應用程式。用於將連線從HTTP升級到WebSocket的HTTP升級機制使用Upgrade和Connection頭。反向代理伺服器在支援WebSocket時面臨一些挑戰。一個是WebSocket是一個逐跳協議,因此當代理伺服器攔截客戶端的升級請求時,需要向後端伺服器傳送自己的升級請求,包括相應的標頭檔案。此外,由於WebSocket連線長期存在,與HTTP使用的典型短期連線相反,反向代理需要允許這些連線保持開啟狀態,而不是關閉它們,因為它們似乎處於空閒狀態。

允許在客戶機和後端伺服器之間建立隧道,NGINX支援WebSocket。對於NGINX將升級請求從客戶端傳送到後臺伺服器,必須明確設定Upgrade和Connection標題。

Nginx開啟websocket代理功能的配置如下:

1)編輯nginx.conf,在http區域內一定要新增下面配置:
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

map指令的作用:
該作用主要是根據客戶端請求中$http_upgrade 的值,來構造改變$connection_upgrade的值,即根據變數$http_upgrade的值建立新的變數$connection_upgrade,
建立的規則就是{}裡面的東西。其中的規則沒有做匹配,因此使用預設的,即 $connection_upgrade 的值會一直是 upgrade。然後如果 $http_upgrade為空字串的話,
那值會是 close。


2)編輯vhosts下虛擬主機的配置檔案,在location匹配配置中新增如下內容:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";

示例如下:
upstream socket.kevin.com {
    hash $remote_addr consistent;
    server 10.0.12.108:9000;
    server 10.0.12.109:9000;
}

 location / {
            proxy_pass http://socket.kevin.com/;
            proxy_set_header Host $host:$server_port;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

WebSocket 機制
WebSocket是HTML5下一種新的協議。它實現了瀏覽器與伺服器全雙工通訊,能更好的節省伺服器資源和頻寬並達到實時通訊的目的。它與HTTP一樣通過已建立的TCP連線來傳輸資料,但是它和HTTP最大不同是:
1) WebSocket是一種雙向通訊協議。在建立連線後,WebSocket伺服器端和客戶端都能主動向對方傳送或接收資料,就像Socket一樣;
2)WebSocket需要像TCP一樣,先建立連線,連線成功後才能相互通訊。

傳統HTTP客戶端與伺服器請求響應模式如下圖所示:

WebSocket模式客戶端與伺服器請求響應模式如下圖:

上圖對比可以看出,相對於傳統HTTP每次請求-應答都需要客戶端與服務端建立連線的模式,WebSocket是類似Socket的TCP長連線通訊模式。一旦WebSocket連線建立後,後續資料都以幀序列的形式傳輸。在客戶端斷開WebSocket連線或Server端中斷連線前,不需要客戶端和服務端重新發起連線請求。在海量併發及客戶端與伺服器互動負載流量大的情況下,極大的節省了網路頻寬資源的消耗,有明顯的效能優勢,且客戶端傳送和接受訊息是在同一個持久連線上發起,實時性優勢明顯。

相比HTTP長連線,WebSocket有以下特點:
1)是真正的全雙工方式,建立連線後客戶端與伺服器端是完全平等的,可以互相主動請求。而HTTP長連線基於HTTP,是傳統的客戶端對伺服器發起請求的模式。
2)HTTP長連線中,每次資料交換除了真正的資料部分外,伺服器和客戶端還要大量交換HTTP header,資訊交換效率很低。Websocket協議通過第一個request建立了TCP連線之後,之後交換的資料都不需要傳送 HTTP header就能交換資料,這顯然和原有的HTTP協議有區別所以它需要對伺服器和客戶端都進行升級才能實現(主流瀏覽器都已支援HTML5)。此外還有 multiplexing、不同的URL可以複用同一個WebSocket連線等功能。這些都是HTTP長連線不能做到的。

總的來說:
WebSocket與Http相同點
-  都是一樣基於TCP的,都是可靠性傳輸協議。
-  都是應用層協議。

WebSocket與Http不同點
-  WebSocket是雙向通訊協議,模擬Socket協議,可以雙向傳送或接受資訊。HTTP是單向的。
-  WebSocket是需要瀏覽器和伺服器握手進行建立連線的。而http是瀏覽器發起向伺服器的連線,伺服器預先並不知道這個連線。

WebSocket與Http聯絡
WebSocket在建立握手時,資料是通過HTTP傳輸的。但是建立之後,在真正傳輸時候是不需要HTTP協議的。

在WebSocket中,只需要伺服器和瀏覽器通過HTTP協議進行一個握手的動作,然後單獨建立一條TCP的通訊通道進行資料的傳送。
WebSocket連線的過程是:
1)客戶端發起http請求,經過3次握手後,建立起TCP連線;http請求裡存放WebSocket支援的版本號等資訊,如:Upgrade、Connection、WebSocket-Version等;
2)伺服器收到客戶端的握手請求後,同樣採用HTTP協議回饋資料;
3)客戶端收到連線成功的訊息後,開始藉助於TCP傳輸通道進行全雙工通訊。

下面再通過客戶端和服務端互動的報文對比WebSocket通訊與傳統HTTP的不同點:
1)在客戶端,new WebSocket例項化一個新的WebSocket客戶端物件,請求類似 ws://yourdomain:port/path 的服務端WebSocket URL,客戶端WebSocket物件會自動解析並識別為WebSocket請求,並連線服務端埠,執行雙方握手過程,客戶端傳送資料格式類似:

GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost:8080
Sec-WebSocket-Version: 13

可以看到,客戶端發起的WebSocket連線報文類似傳統HTTP報文,Upgrade:websocket引數值表明這是WebSocket型別請求,Sec-WebSocket-Key是WebSocket客戶端傳送的一個 base64編碼的密文,要求服務端必須返回一個對應加密的Sec-WebSocket-Accept應答,否則客戶端會丟擲Error during WebSocket handshake錯誤,並關閉連線。
2)服務端收到報文後返回的資料格式類似:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

Sec-WebSocket-Accept的值是服務端採用與客戶端一致的金鑰計算出來後返回客戶端的,HTTP/1.1 101 Switching Protocols表示服務端接受WebSocket協議的客戶端連線,經過這樣的請求-響應處理後,兩端的WebSocket連線握手成功, 後續就可以進行TCP通訊了。

在開發方面,WebSocket API 也十分簡單:只需要例項化 WebSocket,建立連線,然後服務端和客戶端就可以相互傳送和響應訊息。在WebSocket 實現及案例分析部分可以看到詳細的 WebSocket API 及程式碼實現。

騰訊雲公網有日租型別七層負載均衡轉發部分支援Websocket,目前包括英魂之刃、銀漢遊戲等多家企業已接入使用。當出現不相容問題時,請修改websocket配置,websocket server不校驗下圖中圈出的欄位:

比如一個使用WebSocket應用於視訊的業務思路如下:
1)使用心跳維護websocket鏈路,探測客戶端端的網紅/主播是否線上
2)設定負載均衡7層的proxy_read_timeout預設為60s
3)設定心跳為50s,即可長期保持Websocket不斷開

Nginx代理webSocket經常中斷的解決方法(也就是如何保持長連線)

現象描述:用nginx反代代理某個業務,發現平均1分鐘左右,就會出現webSocket連線中斷,然後檢視了一下,是nginx出現的問題。
產生原因:nginx等待第一次通訊和第二次通訊的時間差,超過了它設定的最大等待時間,簡單來說就是超時!

解決方法1
其實只要配置nginx.conf的對應localhost裡面的這幾個引數就好
proxy_connect_timeout;
proxy_read_timeout;
proxy_send_timeout;

解決方法2
發心跳包,原理就是在有效地再讀時間內進行通訊,重新重新整理再讀時間

配置示例:

 http {
    server {
        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass http://webscoket; 
            proxy_http_version 1.1; 
            proxy_connect_timeout 4s;                #配置點1
            proxy_read_timeout 60s;                  #配置點2,如果沒效,可以考慮這個時間配置長一點
            proxy_send_timeout 12s;                  #配置點3
            proxy_set_header Upgrade $http_upgrade;  
            proxy_set_header Connection "Upgrade";   
        }
    }
}

關於上面配置2的解釋
這個是伺服器對你等待最大的時間,也就是說當你webSocket使用nginx轉發的時候,用上面的配置2來說,如果60秒內沒有通訊,依然是會斷開的,所以,你可以按照你的需求來設定。比如說,我設定了10分鐘,那麼如果我10分鐘內有通訊,或者10分鐘內有做心跳的話,是可以保持連線不中斷的,詳細看個人需求

WebSocket與Socket的關係
-  Socket其實並不是一個協議,而是為了方便使用TCP或UDP而抽象出來的一層,是位於應用層和傳輸控制層之間的一組介面。當兩臺主機通訊時,必須通過Socket連線,Socket則利用TCP/IP協議建立TCP連線。TCP連線則更依靠於底層的IP協議,IP協議的連線則依賴於鏈路層等更低層次。
-  WebSocket就像HTTP一樣,則是一個典型的應用層協議。
總的來說:Socket是傳輸控制層介面,WebSocket是應用層協議

相關文章