#!/usr/bin/env ruby
require 'socket'

class PortScanner
  def initialize(host: '0.0.0.0', ports:, batch_size: 1024)
    @host      = host
    @ports     = ports.to_a
    @batch_size = batch_size
  end

  def scan_port(port, timeout)
    peer = Socket.tcp(@host, port, connect_timeout: timeout)
    puts "#{port} #{peer.wait_writable(timeout) ? 'open' : 'timeout'}"
    peer.close
  rescue Errno::ECONNREFUSED
    # puts "#{port} closed"
  rescue SystemCallError => e
    puts "#{port} #{e.message}"
  end

  def start(timeout = 1.0)
    @batch_size.times.map do
      Threadlet.start do
         while port = @ports.shift
           scan_port(port, timeout)
         end
      end
    end.each(&:join)
  end
end

limits = Process.getrlimit(Process::RLIMIT_NOFILE)
batch_size = [512, limits.first].min
host = "127.0.0.1"
scanner = PortScanner.new(host: host, ports: Range.new(1, 65535), batch_size: batch_size)

scanner.start
