patternpythonModerate
Python Port Scanner
Viewed 0 times
scannerpythonport
Problem
This is only my third Python script. Be brutal with me. Any tips, tricks, best practices, or better usages would be great!
import socket
from concurrent.futures import ThreadPoolExecutor
THREADS = 512
CONNECTION_TIMEOUT = 1
def ping(host, port, results = None):
try:
socket.socket().connect((host, port))
if results is not None:
results.append(port)
print(str(port) + " Open")
return True
except:
return False
def scan_ports(host):
available_ports = []
socket.setdefaulttimeout(CONNECTION_TIMEOUT)
with ThreadPoolExecutor(max_workers = THREADS) as executor:
print("\nScanning ports on " + host + " ...")
for port in range(1, 65535):
executor.submit(ping, host, port, available_ports)
print("\nDone.")
available_ports.sort()
print(str(len(available_ports)) + " ports available.")
print(available_ports)
def main():
scan_ports("127.0.0.1")
if __name__ == "__main__":
main()Solution
It's not Pythonic to pass in a mutable object and use that to store results (like it is in C).
Using a catch-all
Also Python lists are thread safe only because of the GIL in CPython. This code would not be thread safe in other implementations such as Jython. Notice how your version always reports open ports in ascending order.
Threads are fine for IO bounded actions, but since pinging localhost is so fast GIL contention slows down performance in this case. Your implementation ran in 27 seconds on my laptop.
By comparison, my implementation ran in 2.76 seconds. Replacing
range(start, stop) is not inclusive of stop, so you have an off by one error as well.Using a catch-all
except line is also poor practice. It's important to catch expected exceptions and reraise the rest.Also Python lists are thread safe only because of the GIL in CPython. This code would not be thread safe in other implementations such as Jython. Notice how your version always reports open ports in ascending order.
Threads are fine for IO bounded actions, but since pinging localhost is so fast GIL contention slows down performance in this case. Your implementation ran in 27 seconds on my laptop.
By comparison, my implementation ran in 2.76 seconds. Replacing
map() with pool.map() led to a runtime of 1.38 seconds:#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from errno import ECONNREFUSED
from functools import partial
from multiprocessing import Pool
import socket
NUM_CORES = 4
def ping(host, port):
try:
socket.socket().connect((host, port))
print(str(port) + " Open")
return port
except socket.error as err:
if err.errno == ECONNREFUSED:
return False
raise
def scan_ports(host):
p = Pool(NUM_CORES)
ping_host = partial(ping, host)
return filter(bool, p.map(ping_host, range(1, 65536)))
def main(host=None):
if host is None:
host = "127.0.0.1"
print("\nScanning ports on " + host + " ...")
ports = list(scan_ports(host))
print("\nDone.")
print(str(len(ports)) + " ports available.")
print(ports)
if __name__ == "__main__":
main()Code Snippets
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from errno import ECONNREFUSED
from functools import partial
from multiprocessing import Pool
import socket
NUM_CORES = 4
def ping(host, port):
try:
socket.socket().connect((host, port))
print(str(port) + " Open")
return port
except socket.error as err:
if err.errno == ECONNREFUSED:
return False
raise
def scan_ports(host):
p = Pool(NUM_CORES)
ping_host = partial(ping, host)
return filter(bool, p.map(ping_host, range(1, 65536)))
def main(host=None):
if host is None:
host = "127.0.0.1"
print("\nScanning ports on " + host + " ...")
ports = list(scan_ports(host))
print("\nDone.")
print(str(len(ports)) + " ports available.")
print(ports)
if __name__ == "__main__":
main()Context
StackExchange Code Review Q#38452, answer score: 11
Revisions (0)
No revisions yet.