Working with TCP Sockets 読書メモ 第19章 Prefork

Working with TCP Sockets 読書メモ 目次

Prefork

  • Preforkパターンでは、接続が来る度にforkするのではなく、サーバ起動時にプロセスをまとめて用意する
  1. メインのサーバプロセスがlistenソケットを作成する
  2. メインのサーバプロセスが一群の子プロセスをforkする
  3. それぞれの子プロセスが共有されたソケットから接続を受け取って、独立して処理する
  4. メインのサーバプロセスは子プロセスを監視する
  • メインのサーバプロセス自身は接続を受け付けない点に注意

実装

require 'socket'
require_relative 'command_handler'

module FTP
  class Preforking
    CRLF        = "\r\n"
    CONCURRENCY = 4

    def initialize(port = 21)
      @control_socket = TCPServer.new(port)
      trap(:INT) { exit }
    end

    def gets
      @client.gets(CRLF)
    end

    def respond(message)
      @client.write(message)
      @client.write(CRLF)
    end

    def run
      child_pids = []

      CONCURRENCY.times do
        child_pids << spawn_child
      end

      trap(:INT) {
        child_pids.each do |cpid|
          begin
            Process.kill(:INT, cpid)
          rescue Errno::ESRCH
            # do nothing
          end
        end

        exit
      }

      loop do
        pid = Process.wait
        $stderr.puts "Process #{pid} quit unexpectedly"

        child_pids.delete(pid)
        child_pids << spawn_child
      end
    end

    def spawn_child
      fork do
        loop do
          @client = @control_socket.accept
          respond "220 OHAI"

          handler = CommandHandler.new(self)

          loop do
            request = gets

            if request
              respond handler.handle(request)
            else
              @client.close
              break
            end
          end
        end
      end
    end
  end
end

server = FTP::Preforking.new(4481)
server.run
  • 親プロセスが終了する際は、必ず子プロセスも合わせて終了するように注意する

考察

  • 利点:プロセス数を制限できる、(スレッドに比べて)プロセス単位で分離されている
  • 欠点:(スレッドに比べて)メモリ消費量が多い

コメントを残す

コメントを残す