欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Nginx如何代理UDP連接

 更新時(shí)間:2023年06月27日 10:06:13   作者:spacewander  
這篇文章主要為大家介紹了Nginx如何代理UDP連接的實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

UDP 連接

眾所周知,UDP 并不像 TCP 那樣是基于連接的。但有些時(shí)候,我們需要往一個(gè)固定的地址發(fā)送多個(gè) UDP 來(lái)完成一個(gè) UDP 請(qǐng)求。為了保證服務(wù)端能夠知道這幾個(gè) UDP 包構(gòu)成同一個(gè)會(huì)話,我們需要在發(fā)送 UDP 包時(shí)綁定某個(gè)端口,這樣當(dāng)網(wǎng)絡(luò)棧通過(guò)五元組(協(xié)議、客戶端IP、客戶端端口、服務(wù)端IP、服務(wù)端端口)進(jìn)行區(qū)分時(shí),那幾個(gè) UDP 包能夠分到一起。通常我們會(huì)把這種現(xiàn)象稱之為 UDP 連接。

但這樣又有了一個(gè)新的問(wèn)題。不同于 TCP 那樣有握手和揮手,UDP 連接僅僅意味著使用固定的客戶端端口。雖然作為服務(wù)端,由于事先就跟客戶端約定好了一套固定的協(xié)議,可以知道一個(gè) UDP 連接應(yīng)當(dāng)在何處終止。但如果中間使用了代理服務(wù)器,那么代理是如何區(qū)分某幾個(gè) UDP 包是屬于某個(gè) UDP 連接呢?畢竟沒(méi)有握手和揮手作為分隔符,一個(gè)中間人是不清楚某個(gè)會(huì)話應(yīng)當(dāng)在何處放下句號(hào)的。

通過(guò)下面的實(shí)驗(yàn),我們會(huì)看到 Nginx 是如何處理這個(gè)問(wèn)題的。

實(shí)驗(yàn)

在接下來(lái)的幾個(gè)實(shí)驗(yàn)中,我都會(huì)用一個(gè)固定的客戶端。這個(gè)客戶端會(huì)向 Nginx 監(jiān)聽的地址建立 UDP “連接”,然后發(fā)送 100 個(gè) UDP 包。

// save it as main.go, and run it like `go run main.go`
package main
import (
    "fmt"
    "net"
    "os"
)
func main() {
    conn, err := net.Dial("udp", "127.0.0.1:1994")
    if err != nil {
        fmt.Printf("Dial err %v", err)
        os.Exit(-1)
    }
    defer conn.Close()
    msg := "H"
    for i := 0; i < 100; i++ {
        if _, err = conn.Write([]byte(msg)); err != nil {
            fmt.Printf("Write err %v", err)
            os.Exit(-1)
        }
    }
}

基礎(chǔ)配置

下面是實(shí)驗(yàn)中用到的 Nginx 基礎(chǔ)配置。后續(xù)實(shí)驗(yàn)都會(huì)在這個(gè)基礎(chǔ)上做些改動(dòng)。

這個(gè)配置中,Nginx 會(huì)有 4 個(gè) worker 進(jìn)程監(jiān)聽 1994 端口,并代理到 1995 端口上。錯(cuò)誤日志會(huì)發(fā)往標(biāo)準(zhǔn)錯(cuò)誤,而訪問(wèn)日志會(huì)發(fā)往標(biāo)準(zhǔn)輸出。

worker_processes  4;
daemon off;
error_log  /dev/stderr warn;
events {
    worker_connections  10240;
}
stream {
    log_format basic '[$time_local] '
                 'received: $bytes_received '
                 '$session_time';
    server {
        listen 1994 udp;
        access_log /dev/stdout basic;
        preread_by_lua_block {
            ngx.log(ngx.ERR, ngx.worker.id(), " ", ngx.var.remote_port)
        }
        proxy_pass 127.0.0.1:1995;
        proxy_timeout 10s;
    }
    server {
        listen 1995 udp;
        return "data";
    }
}

輸出如下:

2023/01/27 18:00:59 [error] 6996#6996: *2 stream [lua] preread_by_lua(nginx.conf:48):2: 1 51933 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
2023/01/27 18:00:59 [error] 6995#6995: *4 stream [lua] preread_by_lua(nginx.conf:48):2: 0 51933 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
2023/01/27 18:00:59 [error] 6997#6997: *1 stream [lua] preread_by_lua(nginx.conf:48):2: 2 51933 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
2023/01/27 18:00:59 [error] 6998#6998: *3 stream [lua] preread_by_lua(nginx.conf:48):2: 3 51933 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:01:09 +0800] received: 28 10.010
[27/Jan/2023:18:01:09 +0800] received: 27 10.010
[27/Jan/2023:18:01:09 +0800] received: 23 10.010
[27/Jan/2023:18:01:09 +0800] received: 22 10.010

可以看出,全部 100 個(gè) UDP 包分散到了每個(gè) worker 進(jìn)程上??磥?lái) Nginx 并沒(méi)有把來(lái)自同一個(gè)地址的 100 個(gè)包當(dāng)作同一個(gè)會(huì)話,畢竟每個(gè)進(jìn)程都會(huì)讀取 UDP 數(shù)據(jù)。

reuseport

要想讓 Nginx 代理 UDP 連接,需要在 listen 時(shí)指定 reuseport:

...
    server {
        listen 1994 udp reuseport;
        access_log /dev/stdout basic;

現(xiàn)在全部 UDP 包都會(huì)落在同一個(gè)進(jìn)程上,并被算作同一個(gè)會(huì)話:

2023/01/27 18:02:39 [error] 7191#7191: *1 stream [lua] preread_by_lua(nginx.conf:48):2: 3 55453 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:02:49 +0800] received: 100 10.010

多個(gè)進(jìn)程在監(jiān)聽同一個(gè)地址時(shí),如果設(shè)置了 reuseport 時(shí),Linux 會(huì)根據(jù)五元組的 hash 來(lái)決定發(fā)往哪個(gè)進(jìn)程。這樣一來(lái),同一個(gè) UDP 連接里面的所有包就會(huì)落到一個(gè)進(jìn)程上。

順便一提,如果在 1995 端口的 server 上打印接受到的 UDP 連接的客戶端地址(即 Nginx 跟上游通信的地址),我們會(huì)發(fā)現(xiàn)同一個(gè)會(huì)話的地址是一樣的。也即是 Nginx 在代理到上游時(shí),默認(rèn)就會(huì)使用 UDP 連接來(lái)傳遞整個(gè)會(huì)話。

proxy_xxx directives

相信讀者也已經(jīng)注意到,在錯(cuò)誤日志中記錄的 UDP 訪問(wèn)開始時(shí)間,和在訪問(wèn)日志中記錄的結(jié)束時(shí)間,正好差了 10 秒。該時(shí)間段對(duì)應(yīng)了配置里的 proxy_timeout 10s;。由于 UDP 連接中沒(méi)有揮手的說(shuō)法,Nginx 默認(rèn)根據(jù)每個(gè)會(huì)話的超時(shí)時(shí)間來(lái)決定會(huì)話何時(shí)終止。默認(rèn)情況下,一個(gè)會(huì)話的持續(xù)時(shí)間是 10 分鐘,只是由于我缺乏耐心,所以特定配成了 10 秒。

除了超時(shí)時(shí)間,Nginx 還會(huì)依靠什么條件決定會(huì)話的終止呢?請(qǐng)往下看:

...
        proxy_timeout 10s;
        proxy_responses 1;

在新增了 proxy_responses 1 后,輸出變成了這樣:

2023/01/27 18:07:35 [error] 7552#7552: *1 stream [lua] preread_by_lua(nginx.conf:48):2: 2 36308 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:07:35 +0800] received: 62 0.003
2023/01/27 18:07:35 [error] 7552#7552: *65 stream [lua] preread_by_lua(nginx.conf:48):2: 2 36308 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:07:35 +0800] received: 9 0.000
2023/01/27 18:07:35 [error] 7552#7552: *76 stream [lua] preread_by_lua(nginx.conf:48):2: 2 36308 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:07:35 +0800] received: 7 0.000
2023/01/27 18:07:35 [error] 7552#7552: *85 stream [lua] preread_by_lua(nginx.conf:48):2: 2 36308 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:07:35 +0800] received: 3 0.000
2023/01/27 18:07:35 [error] 7552#7552: *90 stream [lua] preread_by_lua(nginx.conf:48):2: 2 36308 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:07:35 +0800] received: 19 0.000

我們看到 Nginx 不再被動(dòng)等待時(shí)間超時(shí),而是在收到上游發(fā)來(lái)的包之后就終止了會(huì)話。proxy_timeout 和 proxy_responses 兩者間是“或”的關(guān)系。

和 proxy_responses 相對(duì)的有一個(gè) proxy_requests

...
        proxy_timeout 10s;
        proxy_responses 1;
        proxy_requests 50;

在配置了 proxy_requests 50 后,我們會(huì)看到每個(gè)請(qǐng)求的大小都穩(wěn)定在 50 個(gè) UDP 包:

2023/01/27 18:08:55 [error] 7730#7730: *1 stream [lua] preread_by_lua(nginx.conf:48):2: 0 49881 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
2023/01/27 18:08:55 [error] 7730#7730: *11 stream [lua] preread_by_lua(nginx.conf:48):2: 0 49881 while prereading client data, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:08:55 +0800] received: 50 0.002
[27/Jan/2023:18:08:55 +0800] received: 50 0.001

注意讓會(huì)話終止所需的上游響應(yīng)的 UDP 數(shù)是 proxy_requests * proxy_responses。在上面的例子中,如果我們把 proxy_responses 改成 2,那么要過(guò) 10 秒才會(huì)終止會(huì)話。因?yàn)檫@么做之后,對(duì)應(yīng)每 50 個(gè) UDP 包的請(qǐng)求,需要響應(yīng) 100 個(gè) UDP 包才會(huì)終止會(huì)話,而每個(gè)請(qǐng)求的 UDP 包只會(huì)得到一個(gè) UDP 作為響應(yīng),所以只能等超時(shí)了。

動(dòng)態(tài)代理

在大多數(shù)時(shí)候,UDP 請(qǐng)求的包數(shù)不是固定的,我們可能要根據(jù)開頭的某個(gè)長(zhǎng)度字段來(lái)確定會(huì)話的包數(shù),抑或通過(guò)某個(gè)包的包頭是否有 eof 標(biāo)記來(lái)判斷什么時(shí)候終結(jié)當(dāng)前會(huì)話。目前 Nginx 的幾個(gè) proxy_* 指令都只支持固定值,不支持借助變量動(dòng)態(tài)設(shè)置。

proxy_requests 和 proxy_responses 實(shí)際上只是設(shè)置了 UDP session 上的對(duì)應(yīng)計(jì)數(shù)器。所以理論上我們可以修改 Nginx,暴露出 API 來(lái)動(dòng)態(tài)調(diào)整當(dāng)前 UDP session 的計(jì)數(shù)器的值,實(shí)現(xiàn)按上下文決定 UDP 請(qǐng)求邊界的功能。那是否存在不修改 Nginx 的解決方案呢?

換個(gè)思路,我們能不能通過(guò) Lua 把客戶端數(shù)據(jù)都讀出來(lái),然后在 Lua 層面上由 cosocket 發(fā)送給上游?通過(guò) Lua 實(shí)現(xiàn)上游代理這個(gè)思路確實(shí)挺富有想象力,可惜它目前是行不通的。

使用如下代碼代替前面的 preread_by_lua_block

content_by_lua_block {
            local sock = ngx.req.socket()
            while true do
                local data, err = sock:receive()
                if not data then
                    if err and err ~= "no more data" then
                        ngx.log(ngx.ERR, err)
                    end
                    return
                end
                ngx.log(ngx.WARN, "message received: ", data)
            end
        }
        proxy_timeout 10s;
        proxy_responses 1;
        proxy_requests 50;

我們會(huì)看到這樣的輸出:2023/01/27 18:17:56 [warn] 8645#8645: *1 stream [lua] content_by_lua(nginx.conf:59):12: message received: H, udp client: 127.0.0.1, server: 0.0.0.0:1994
[27/Jan/2023:18:17:56 +0800] received: 1 0.000
...

由于在 UDP 下面, ngx.req.socket:receive 目前只支持讀取第一個(gè)包,所以即使我們?cè)O(shè)置了 while true 循環(huán),也得不到全部的客戶端請(qǐng)求。另外由于 content_by_lua 會(huì)覆蓋掉 proxy_* 指令,所以 Nginx 并沒(méi)有走代理邏輯,而是認(rèn)為當(dāng)前請(qǐng)求只有一個(gè)包。把 content_by_lua 改成 preread_by_lua 后,雖然 proxy_* 指令這下子生效了,但因?yàn)槟貌坏饺靠蛻舳苏?qǐng)求,依然無(wú)法完成 Lua 層面上的代理。

總結(jié)

假如 Nginx 代理的是 DNS 這種只有一個(gè)包的基于 UDP 的協(xié)議,那么使用 listen udp 就夠了。但如果需要代理包含多個(gè)包的基于 UDP 的協(xié)議,那么還需要加上 reuseport。另外,Nginx 目前還不支持動(dòng)態(tài)設(shè)置每個(gè) UDP 會(huì)話的大小,所以沒(méi)辦法準(zhǔn)確區(qū)分不同的 UDP 會(huì)話。Nginx 代理 UDP 協(xié)議時(shí)能用到的功能,更多集中于像限流這種不需要關(guān)注單個(gè) UDP 會(huì)話的。

以上就是詳解Nginx如何代理UDP連接的詳細(xì)內(nèi)容,更多關(guān)于Nginx代理UDP連接的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Nginx四層負(fù)載均衡的實(shí)現(xiàn)示例

    Nginx四層負(fù)載均衡的實(shí)現(xiàn)示例

    Nginx?不支持傳統(tǒng)的四層負(fù)載均衡,但可以通過(guò)stream模塊配合TCP實(shí)現(xiàn)類似的功能,本文主要介紹了Nginx四層負(fù)載均衡的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-04-04
  • Nginx實(shí)現(xiàn)接口復(fù)制的示例代碼

    Nginx實(shí)現(xiàn)接口復(fù)制的示例代碼

    本文主要介紹了使用Nginx的mirror指令和Lua腳本實(shí)現(xiàn)接口流復(fù)制,方便將請(qǐng)求同時(shí)轉(zhuǎn)發(fā)到多個(gè)后端服務(wù)器,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-01-01
  • Nginx三種不同類型的虛擬主機(jī)的配置(基于域名、IP 和端口)

    Nginx三種不同類型的虛擬主機(jī)的配置(基于域名、IP 和端口)

    本文主要介紹了Nginx多種虛擬主機(jī)配置方式,能夠根據(jù)域名、IP 或端口區(qū)分不同的站點(diǎn),本文就來(lái)介紹一下,感興趣的可以了解一下
    2025-04-04
  • Nginx報(bào)錯(cuò)host not found in upstream的解決辦法

    Nginx報(bào)錯(cuò)host not found in upstream的解決辦法

    本文主要介紹了Nginx報(bào)錯(cuò)host not found in upstream的解決辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • linux查找當(dāng)前系統(tǒng)nginx路徑的兩種方法

    linux查找當(dāng)前系統(tǒng)nginx路徑的兩種方法

    工作中有很多服務(wù)器, 它們上面裝的 nginx 的路徑也太不相當(dāng), 當(dāng)我們拿到一個(gè)不熟悉的服務(wù)器時(shí), 我們?cè)趺粗? 當(dāng)前運(yùn)行的nginx的目錄是哪一個(gè)呢,本文小編給大家介紹了兩種linux查找當(dāng)前系統(tǒng)nginx的路徑的方法,需要的朋友可以參考下
    2023-11-11
  • Nginx搭建自己的CDN服務(wù)器的方法步驟

    Nginx搭建自己的CDN服務(wù)器的方法步驟

    本文主要介紹了Nginx搭建自己的CDN服務(wù)器的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-02-02
  • 關(guān)于nginx+uWsgi配置遇到的問(wèn)題的解決

    關(guān)于nginx+uWsgi配置遇到的問(wèn)題的解決

    uWSGI 是在像 nginx 、 lighttpd 以及 cherokee 服務(wù)器上的一個(gè)部署的選擇,本篇文章主要介紹了關(guān)于nginx+uWsgi配置遇到的問(wèn)題的解決,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • 使用nginx緩存服務(wù)器上靜態(tài)文件的設(shè)置方法

    使用nginx緩存服務(wù)器上靜態(tài)文件的設(shè)置方法

    這篇文章主要介紹了使用nginx緩存服務(wù)器上的靜態(tài)文件,文中給大家提到了nginx緩存的優(yōu)點(diǎn)及設(shè)置方法,通過(guò)實(shí)例相結(jié)合的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2018-05-05
  • nginx 部署 vue 項(xiàng)目找不到j(luò)s css文件的解決方法

    nginx 部署 vue 項(xiàng)目找不到j(luò)s css文件的解決方法

    這篇文章主要介紹了nginx 部署 vue 項(xiàng)目找不到j(luò)s css文件的解決方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-07-07
  • windows安裝nginx部署步驟圖解(反向代理與負(fù)載均衡)

    windows安裝nginx部署步驟圖解(反向代理與負(fù)載均衡)

    這篇文章主要介紹了windows安裝nginx部署步驟,設(shè)置反向代理與負(fù)載均衡的使用方法,需要的朋友可以參考下
    2014-02-02

最新評(píng)論