-
-
Save picatz/e8a6f526ce236481837f3cdbb9b84152 to your computer and use it in GitHub Desktop.
require 'async/io' | |
require 'async/await' | |
require 'async/semaphore' | |
class PortScanner | |
include Async::Await | |
include Async::IO | |
def initialize(host: '127.0.0.1', ports:) | |
@host = host | |
@ports = ports | |
@semaphore = Async::Semaphore.new(`ulimit -n`.to_i) | |
end | |
def scan_port(port, timeout: 0.5) | |
timeout(timeout) do | |
Async::IO::Endpoint.tcp(@host, port).connect do |peer| | |
peer.close | |
puts "#{port} open" | |
end | |
end | |
rescue Errno::ECONNREFUSED, Async::TimeoutError | |
puts "#{port} closed" | |
rescue Errno::EMFILE | |
sleep timeout | |
retry | |
end | |
async def start(timeout: 0.5) | |
@ports.map do |port| | |
@semaphore.async do | |
scan_port(port, timeout: timeout) | |
end | |
end.collect(&:result) | |
end | |
end | |
scanner = PortScanner.new(ports: (1..1024)) | |
scanner.start |
The syncing part is pretty cool! Not totally needed for my cases, but a really nice touch. ๐
@picatz Because of the way async works - scanner.start
will wait until all tasks complete anyway.
If you add
require 'async/semaphore'
def initialize
@semaphore = Async::Semaphore.new(`ulimit -n`.to_i)
...
end
# remove async from def scan_port
async def start(timeout: 0.5)
@ports.map{|port|
@semaphore.async do
scan_port(port, timeout: timeout)}
end
end.collect(&:result)
end
You should avoid the need for handling Errno::EMFILE
.
You need to use async
v1.9.1 for this to work.
@ioquatix Async::Semaphore
seems to be a nice abstraction to help clean up the code and provide a similar semantic for people that understand Ruby's multi-threading APIs. ๐
However, I still seem to reach the too many files limit since, I'd assume, other operations on my system have files that are open. So, I still end up hitting Errno::EMFILE
, or using it to keep track of the situation myself with raise Errno::EMFILE while @open_fds >= @fd_limit
which I don't mind doing in this case -- I think.
To add synchronisation to the port scanner, try adding
Then, instead of printing the result you can return it.