V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
daokedao
V2EX  ›  Go 编程语言

Go 的 http 服务器,同时下载的连接不能超过两个吗?

  •  
  •   daokedao · 2022-06-10 11:12:08 +08:00 · 4131 次点击
    这是一个创建于 679 天前的主题,其中的信息可能已经有所发展或是发生改变。

    一个最简单的 http 服务器:

    func main() {
    	fs := http.FileServer( http.Dir("./public"))
    	http.Handle("/", fs)
    	http.ListenAndServe(":8888", nil)
    }
    

    用 curl 测试几个连接同时下载:

    curl --limit-rate 1k http://1.2.3.4:9999/file.zip --output NUL
    

    结果是,只有第一个和第二个连接正常下载,其他的连接处于停滞状态,只有前面的连接下载完了,后面的才能接着下载。

    如果把服务器换成 nginx 则多个连接都可以同时下载。

    以上都是在 Windows 11 下进行的 Go 的版本是 1.18.1

    38 条回复    2022-06-13 18:15:27 +08:00
    shakukansp
        1
    shakukansp  
       2022-06-10 11:17:59 +08:00
    你就不能在 fs 里面加个 go 么
    keepeye
        2
    keepeye  
       2022-06-10 11:32:07 +08:00   ❤️ 1
    跟你一样的环境,一样的代码,我测试可以并发下载
    daokedao
        3
    daokedao  
    OP
       2022-06-10 11:32:57 +08:00
    @shakukansp 抱歉我没看懂,如何在 fs 里面加个 go
    daokedao
        4
    daokedao  
    OP
       2022-06-10 11:34:51 +08:00
    @keepeye 我反复多次都是只能两个连接同时下载,我也不知道我哪不对,所有又用了 nginx 试
    shakukansp
        5
    shakukansp  
       2022-06-10 11:44:25 +08:00
    @daokedao 对不起我错了,http 本来就是协程的
    jakes
        6
    jakes  
       2022-06-10 11:45:18 +08:00
    http.ListenAndServe 里面就有 go c.serve(connCtx),不需要 fs 再 go 了吧?
    beordle
        7
    beordle  
       2022-06-10 12:52:45 +08:00 via iPhone
    可能说监听队列长度问题。你可以 google 下如何指定。大概率是你 nil 的那个位置。
    pathletboy
        8
    pathletboy  
       2022-06-10 13:09:05 +08:00
    我猜是写入了同个文件 NUL ?
    daokedao
        9
    daokedao  
    OP
       2022-06-10 14:57:45 +08:00
    @keepeye @beordle @pathletboy 问题很奇怪,好像和下载的文件大小有关,测试如下:

    如果我用小文件(小于 100KB )可以多个同时下载
    如果文件大于 1MB 就只能两个同时下载,其他的等待

    莫名其妙啊
    keepeye
        10
    keepeye  
       2022-06-10 15:21:48 +08:00
    @daokedao 应该不是 go 的问题,我测试的文件几十 MB ,对了,我 curl 命令是在 wsl 里面执行的
    daokedao
        11
    daokedao  
    OP
       2022-06-10 15:24:50 +08:00
    @keepeye 我是打开了四个 cmd 窗口,用 curl 下载,我再试试
    dreasky
        12
    dreasky  
       2022-06-10 15:54:48 +08:00
    不会是 cmd 窗口卡住了吧 :doge
    pathletboy
        13
    pathletboy  
       2022-06-10 15:59:01 +08:00
    @daokedao
    http.DefaultTransport = &http.Transport{
    DisableKeepAlives: true,
    }
    配置下可以,原因未知
    daokedao
        14
    daokedao  
    OP
       2022-06-10 18:36:17 +08:00
    @dreasky 用同样的 cmd 窗口,从 nginx 下载就没有问题。

    @pathletboy 试了 http.DefaultTransport ,也不行。另外我查了 http.DefaultTransport 是用在 client 端的,服务端是用 SetKeepAlivesEnabled(false) ,试了还是不行。
    ToBeHacker
        15
    ToBeHacker  
       2022-06-10 20:12:55 +08:00   ❤️ 1
    nul 文件锁
    Frankcox
        16
    Frankcox  
       2022-06-11 08:03:37 +08:00   ❤️ 1
    浏览器这没复现成功,Chrome windows11 go1.18.1 四个标签页下载,都能正常下载。
    AnroZ
        17
    AnroZ  
       2022-06-11 10:13:16 +08:00
    go 默认运行在一个 cpu 核上的,尝试设置下 runtime.GOMAXPROCS
    daokedao
        18
    daokedao  
    OP
       2022-06-11 18:02:23 +08:00
    @ToBeHacker 不是 NUL 的问题,即使把 NUL 改为不同的文件名,结果同样。

    @AnroZ 网上可查得 Starting from Go 1.5, the default value of GOMAXPROCS is the number of cores.
    daokedao
        19
    daokedao  
    OP
       2022-06-11 18:03:53 +08:00
    @Frankcox 我也用 Chrome 试了,连续下载四次,开始时只有前两个下载有进度,后两个下载进度一直显示 0 。
    但是过了一会,神奇的事情发生了,第三个下载在一瞬间完成了。又过了一会,第四个也在一瞬间完成了。
    而这时,第一个和第二个还在下载中。。。
    daokedao
        20
    daokedao  
    OP
       2022-06-11 18:17:24 +08:00
    @Frankcox 上面一瞬间完成的下载,找到原因了,是下载出错了,只下载了 1KB ,而原文件是 72MB

    我又用 Chrome 反复试了,结论还是只能有两个连接同时下载,后面的连接要等前面的结束后才开始下载。
    Kisesy
        21
    Kisesy  
       2022-06-11 18:20:14 +08:00   ❤️ 1
    试了一下还真是,第一个和第二个正常下载,第三个就停住不动,如果结束掉其中一个,第三个就正常下载了
    daokedao
        22
    daokedao  
    OP
       2022-06-11 18:22:59 +08:00
    @Kisesy 终于能复现了 🤣🤣🤣
    Frankcox
        23
    Frankcox  
       2022-06-11 18:23:57 +08:00   ❤️ 1
    @daokedao 我刚刚又试了下,还是没有出现你的问题,同时下载四个 10G 左右的视频。两个视频速度 200mb/s 多,两个视频 50mb/s 左右。其中只有一个视频初始下载速度大概 100B/s 持续了 3 秒左右就正常了。
    daokedao
        24
    daokedao  
    OP
       2022-06-11 18:33:29 +08:00
    @Frankcox 可我这怎么都不行
    Kisesy
        25
    Kisesy  
       2022-06-11 19:02:17 +08:00   ❤️ 1
    用 aria2 测试也是这样, 就算是 aria2 和 curl 混用也不能超过 2 个任务, 不知道为什么
    daokedao
        26
    daokedao  
    OP
       2022-06-11 19:11:43 +08:00
    @Kisesy 我用 curl 和 chrome 混用也不能超过两个
    AnroZ
        27
    AnroZ  
       2022-06-12 01:54:55 +08:00   ❤️ 1
    刚刚测试了下,的确如此,第三个速度刚开始有速度但马上变为 0 了。
    这应该 go http 包实现的问题,有空的话,可以仔细看下实现。
    因为我换成 echo 框架,实现也是调用 http.ServeFile 的,测试到 5 个,都没问题。
    haoliang
        28
    haoliang  
       2022-06-12 11:41:07 +08:00
    我觉得能确定的是:即使是单核 go 也能并发处理请求,GOMAXPROCS 并没有带来实质变化。
    haoliang
        29
    haoliang  
       2022-06-12 11:54:04 +08:00
    读了一圈源码,并没有发现问题,敢问楼主啥硬件?
    ```
    for { rw = l.accept(); go Conn(rw).serve(); }
    ```
    不过 fileServer 没有用 sendfile 而是用 io.copy 让我比较惊讶
    daokedao
        30
    daokedao  
    OP
       2022-06-12 12:03:18 +08:00
    @haoliang 我这台电脑 cpu 是 5700g ,系统是 Windows 11 ,Go 版本是 1.18.1 windows/amd64
    Kisesy
        31
    Kisesy  
       2022-06-12 12:58:42 +08:00


    感觉跟用 netutil 的 LimitListener 函数限制了并发一样
    不一样的是如果用 LimitListener 限制了,这个 c 任务是读 0 字节,但这个会读 512 字节
    AnroZ
        32
    AnroZ  
       2022-06-12 23:54:12 +08:00
    而且,同样的代码在 WSL 子系统上运行是不存在这个问题
    keepeye
        33
    keepeye  
       2022-06-13 10:02:39 +08:00
    我还是无法复现,curl 也改成 win 下的了,windows terminal + pwsh7 + curl ,4 个窗口同时下载,速度都正常

    $ curl --limit-rate 1k http://127.0.0.1:8989/DesktopOK_Installer_x64.zip --output NUL
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    3 594k 3 18944 0 0 1021 0 0:09:56 0:00:18 0:09:38 1025


    $ curl --limit-rate 1k http://127.0.0.1:8989/DesktopOK_Installer_x64.zip --output NUL
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    3 594k 3 18944 0 0 1021 0 0:09:56 0:00:18 0:09:38 1023


    $ curl --limit-rate 1k http://127.0.0.1:8989/DesktopOK_Installer_x64.zip --output NUL
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    2 594k 2 15872 0 0 1020 0 0:09:57 0:00:15 0:09:42 1020


    $ curl --limit-rate 1k http://127.0.0.1:8989/DesktopOK_Installer_x64.zip --output NUL
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    2 594k 2 15872 0 0 1020 0 0:09:57 0:00:15 0:09:42 1020
    daokedao
        34
    daokedao  
    OP
       2022-06-13 10:35:11 +08:00
    我现在是按照 @AnroZ 的方法,换成 echo 或 gin ,测试都没有问题
    whoami9894
        35
    whoami9894  
       2022-06-13 16:12:44 +08:00   ❤️ 7
    http.FileServer 在 Windows 的最底层调用 TransmitFile ( https://github.com/golang/go/blob/master/src/internal/poll/sendfile_windows.go#L61)

    TransmitFile 在个人机限制并发数为 2 ,服务器无限制 ( https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile#remarks)

    > Workstation and client versions of Windows optimize the TransmitFile function for minimum memory and resource utilization by limiting the number of concurrent TransmitFile operations allowed on the system to a maximum of two. On Windows Vista, Windows XP, Windows 2000 Professional, and Windows NT Workstation 3.51 and later only two outstanding TransmitFile requests are handled simultaneously; the third request will wait until one of the previous requests is completed.

    Server versions of Windows optimize the TransmitFile function for high performance. On server versions, there are no default limits placed on the number of concurrent TransmitFile operations allowed on the system. Expect better performance results when using TransmitFile on server versions of Windows. On server versions of Windows, it is possible to set a limit on the maximum number of concurrent TransmitFile operations by creating a registry entry and setting a value for the following REG_DWORD:

    HKEY_LOCAL_MACHINE\CurrentControlSet\Services\AFD\Parameters\MaxActiveTransmitFileCount
    whoami9894
        36
    whoami9894  
       2022-06-13 16:49:59 +08:00   ❤️ 2
    至于为什么 gin 可以,因为它根本没做这个优化

    16 年有人发过 pr ( https://github.com/gin-gonic/gin/pull/638),但不知道什么原因关闭了,一直到今天都没实现这个优化



    ```go
    // gin
    8 0x000000000054620e in net.(*TCPConn).Write
    at <autogenerated>:1
    9 0x00000000005de533 in net/http.checkConnErrorWriter.Write
    at d:/go/src/net/http/server.go:3532
    10 0x0000000000592e75 in bufio.(*Writer).Write
    at d:/go/src/bufio/bufio.go:639
    11 0x00000000005d2695 in net/http.(*chunkWriter).Write
    at d:/go/src/net/http/server.go:383
    12 0x0000000000592e75 in bufio.(*Writer).Write
    at d:/go/src/bufio/bufio.go:639
    13 0x00000000005d819e in net/http.(*response).write
    at d:/go/src/net/http/server.go:1592
    14 0x00000000005d7ed0 in net/http.(*response).Write
    at d:/go/src/net/http/server.go:1550
    15 0x00000000006f3078 in github.com/gin-gonic/gin.(*responseWriter).Write
    at c:/users/eddisonwang/go/pkg/mod/github.com/gin-gonic/[email protected]/response_writer.go:78
    16 0x0000000000452444 in io.copyBuffer
    at d:/go/src/io/io.go:425
    17 0x00000000004520fa in io.Copy
    at d:/go/src/io/io.go:382
    18 0x00000000004520fa in io.CopyN
    at d:/go/src/io/io.go:358
    19 0x00000000005b2ba5 in net/http.serveContent
    at d:/go/src/net/http/fs.go:337
    20 0x00000000005b4af5 in net/http.serveFile
    at d:/go/src/net/http/fs.go:664




    // std http
    6 0x0000000000f0ed5c in net.sendFile
    at d:/go/src/net/sendfile_windows.go:37
    7 0x0000000000f1196e in net.(*TCPConn).readFrom
    at d:/go/src/net/tcpsock_posix.go:52
    8 0x0000000000f10f76 in net.(*TCPConn).ReadFrom
    at d:/go/src/net/tcpsock.go:104
    9 0x0000000000fa08d9 in net/http.(*response).ReadFrom
    at d:/go/src/net/http/server.go:597
    10 0x0000000000e65a8b in io.copyBuffer
    at d:/go/src/io/io.go:409
    11 0x0000000000e657fa in io.Copy
    at d:/go/src/io/io.go:382
    12 0x0000000000e657fa in io.CopyN
    at d:/go/src/io/io.go:358
    13 0x0000000000f80465 in net/http.serveContent
    at d:/go/src/net/http/fs.go:337
    14 0x0000000000f823b5 in net/http.serveFile
    at d:/go/src/net/http/fs.go:664
    ```
    daokedao
        37
    daokedao  
    OP
       2022-06-13 17:18:13 +08:00
    @whoami9894 应该就是这个原因了,佩服 👍👍👍
    Kisesy
        38
    Kisesy  
       2022-06-13 18:15:27 +08:00
    @whoami9894 感谢啊,感觉就是有把大锁锁住了文件,原来是这个函数,微软也太抠了,限制到 2
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4750 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 50ms · UTC 09:56 · PVG 17:56 · LAX 02:56 · JFK 05:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.