亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

>

目錄
  • UDP 連接
  • 實(shí)驗(yàn)
    • 基礎(chǔ)配置
    • reuseport
    • proxy_xxx directives
    • 動態(tài)代理
  • 總結(jié)

    UDP 連接

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

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

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

    實(shí)驗(yàn)

    在接下來的幾個實(shí)驗(yàn)中,我都會用一個固定的客戶端。這個客戶端會向 Nginx 監(jiān)聽的地址建立 UDP “連接”,然后發(fā)送 100 個 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)都會在這個基礎(chǔ)上做些改動。

    這個配置中,Nginx 會有 4 個 worker 進(jìn)程監(jiān)聽 1994 端口,并代理到 1995 端口上。錯誤日志會發(fā)往標(biāo)準(zhǔn)錯誤,而訪問日志會發(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 個 UDP 包分散到了每個 worker 進(jìn)程上。看來 Nginx 并沒有把來自同一個地址的 100 個包當(dāng)作同一個會話,畢竟每個進(jìn)程都會讀取 UDP 數(shù)據(jù)。

    reuseport

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

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

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

    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

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

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

    proxy_xxx directives

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

    除了超時時間,Nginx 還會依靠什么條件決定會話的終止呢?請往下看:

    ...
            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 不再被動等待時間超時,而是在收到上游發(fā)來的包之后就終止了會話。proxy_timeout 和 proxy_responses 兩者間是“或”的關(guān)系。

    和 proxy_responses 相對的有一個 proxy_requests

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

    在配置了 proxy_requests 50 后,我們會看到每個請求的大小都穩(wěn)定在 50 個 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

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

    動態(tài)代理

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

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

    換個思路,我們能不能通過 Lua 把客戶端數(shù)據(jù)都讀出來,然后在 Lua 層面上由 cosocket 發(fā)送給上游?通過 Lua 實(shí)現(xiàn)上游代理這個思路確實(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;

    我們會看到這樣的輸出: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 目前只支持讀取第一個包,所以即使我們設(shè)置了 while true 循環(huán),也得不到全部的客戶端請求。另外由于 content_by_lua 會覆蓋掉 proxy_* 指令,所以 Nginx 并沒有走代理邏輯,而是認(rèn)為當(dāng)前請求只有一個包。把 content_by_lua 改成 preread_by_lua 后,雖然 proxy_* 指令這下子生效了,但因?yàn)槟貌坏饺靠蛻舳苏埱螅廊粺o法完成 Lua 層面上的代理。

    總結(jié)

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

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

    >

    分享到:
    標(biāo)簽:nginx 代理 服務(wù)器 詳解 連接
    用戶無頭像

    網(wǎng)友整理

    注冊時間:

    網(wǎng)站:5 個   小程序:0 個  文章:12 篇

    • 51998

      網(wǎng)站

    • 12

      小程序

    • 1030137

      文章

    • 747

      會員

    趕快注冊賬號,推廣您的網(wǎng)站吧!
    最新入駐小程序

    數(shù)獨(dú)大挑戰(zhàn)2018-06-03

    數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

    答題星2018-06-03

    您可以通過答題星輕松地創(chuàng)建試卷

    全階人生考試2018-06-03

    各種考試題,題庫,初中,高中,大學(xué)四六

    運(yùn)動步數(shù)有氧達(dá)人2018-06-03

    記錄運(yùn)動步數(shù),積累氧氣值。還可偷

    每日養(yǎng)生app2018-06-03

    每日養(yǎng)生,天天健康

    體育訓(xùn)練成績評定2018-06-03

    通用課目體育訓(xùn)練成績評定