代码之家  ›  专栏  ›  技术社区  ›  mverzilli

如何使用HTTP::Server同时传输多个文件?

  •  3
  • mverzilli  · 技术社区  · 7 年前

    我正在开发一个服务于大文件的HTTP服务。我注意到并行下载是不可能的。该过程一次只提供一个文件,所有其他下载都在等待,直到以前的下载完成。如何同时传输多个文件?

        require "http/server"
    
        server = HTTP::Server.new(3000) do |context|
          context.response.content_type = "application/data"
          f = File.open "bigfile.bin", "r"
          IO.copy f, context.response.output
        end
    
        puts "Listening on http://127.0.0.1:3000"
        server.listen
    

        $ ab -n 10 -c 1 127.0.0.1:3000/
    
        [...]
        Percentage of the requests served within a certain time (ms)
         50%      9
         66%      9
         75%      9
         80%      9
         90%      9
         95%      9
         98%      9
         99%      9
        100%      9 (longest request)
    

    一次请求10个文件:

        $ ab -n 10 -c 10 127.0.0.1:3000/
    
        [...]
        Percentage of the requests served within a certain time (ms)
         50%     52
         66%     57
         75%     64
         80%     69
         90%     73
         95%     73
         98%     73
         99%     73
        100%     73 (longest request)
    
    1 回复  |  直到 7 年前
        1
  •  6
  •   mverzilli    7 年前

    File#read context.response.output 永远不会阻塞。Crystal的并发模型基于协作调度光纤,其中只有在IO阻塞时才会切换光纤。使用非阻塞IO从磁盘读取是不可能的,这意味着唯一可能阻塞的部分是写入 context.response.output 但是,在同一台机器上,磁盘IO比网络IO慢得多,这意味着写操作永远不会阻塞,因为ab的读取速度远远快于磁盘提供数据的速度,即使是从磁盘缓存中读取数据。这个例子实际上是打破crystal并发性的完美风暴。

    使命感 Fiber.yield 将执行传递给任何挂起的光纤是正确的解决方案。下面是一个如何在读取文件时阻止(并屈服)的示例:

        def copy_in_chunks(input, output, chunk_size = 4096)
          size = 1
          while size > 0
            size = IO.copy(input, output, chunk_size)
            Fiber.yield
          end
        end
    
        File.open("bigfile.bin", "r") do |file|
          copy_in_chunks(file, context.response)
        end
    

    这是这里讨论的抄本: https://github.com/crystal-lang/crystal/issues/4628