3
\$\begingroup\$

I'm writing code to find the available ports on 127.0.0.1. I'd like it to be faster, but it's functional now.

def find_open_ports():
    for port in xrange(1,8081):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        res = sock.connect_ex(('localhost', port))
        if res == 0:
            yield port
        sock.close()

and it is called with available_ports = list(find_open_ports()).

There is no error handling because this will be running server-side. If localhost can't be found, there are bigger problems than failing a port scan.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Note: This snippet finds ports already "open" i.e. some service already serving traffic, but not ports "available" to start a new service. \$\endgroup\$
    – RayLuo
    Oct 6, 2020 at 22:51

2 Answers 2

3
\$\begingroup\$

It's always good to use a context manager always. You can wrap the socket in contextlib.closing().

I know your question is tagged specifically , but I suggest to use range instead of xrange anyway, because you don't really lose anything with it, and the script becomes compatible with Python 3.

from contextlib import closing
import socket


def find_open_ports():
    for port in range(1, 8081):
        with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
            res = sock.connect_ex(('localhost', port))
            if res == 0:
                yield port

Note that in Python 3 you wouldn't need closing, you could write simply:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
\$\endgroup\$
2
  • \$\begingroup\$ I always assumed xrange would be faster than range because xrange is a generator instead of a list? \$\endgroup\$
    – erip
    Jan 11, 2016 at 17:54
  • \$\begingroup\$ It depends. In any case, range will not be your bottleneck here, because it will be far outweighed by network I/O \$\endgroup\$
    – janos
    Jan 11, 2016 at 17:56
4
\$\begingroup\$

Two comments:

  1. The existing code only checks for free ports under 8081. The full range is 0-65535.

  2. You're only checking the IPv4 definition of 'localhost'. We live in the future! IPv6 calls localhost ::1. At first blush, this would seem to not matter - something's either listening or not, right? - but it turns out that firewalls sometimes have different rulesets for IPv4 vs IPv6, so one may get through but the other not. You didn't specify what this code was for, so it's just a caveat to keep in mind.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.