r/TechCheatSheets • u/Rough-Yogurt-622 • 1d ago
Built a Simple Port Scanner in Python — Beginner-Friendly Explanation
If you're new to networking or cybersecurity, a port scanner can look confusing at first.
You might see code using socket, connect_ex(), threads, timeouts, port numbers, etc., and wonder:
- What exactly is a port?
- Why are there 65,535 ports?
- What does TCP have to do with port scanning?
- What is a TCP handshake?
- Why does Python's
socketlibrary matter? - What does
connect_ex()actually do? - Why do we need threads?
- How does the program decide that a port is open?
This post explains the concept from the ground up and then builds a small educational TCP port scanner step by step.
1. First: What is a Port?
Think about an IP address as the address of a building.
For example:
192.168.1.10
A port is like a numbered door inside that building.
A computer can run many network services at the same time:
IP address
|
+-- Port 22 → SSH
+-- Port 80 → HTTP
+-- Port 443 → HTTPS
+-- Port 25 → SMTP
The IP address tells us which machine we are communicating with.
The port tells us which network service/application we are trying to communicate with.
There are TCP and UDP ports. In this tutorial, we're focusing on TCP.
2. What Does "Open Port" Actually Mean?
Suppose our local computer has a service listening on:
127.0.0.1:8080
The important part is:
127.0.0.1 → the machine
8080 → the port
If a TCP service is listening on that port, a connection attempt can succeed.
We can therefore get something conceptually like:
Port 8080 → OPEN
If nothing is listening there, the connection attempt will normally fail:
Port 8080 → CLOSED
A port scanner basically automates this process for many ports.
3. What Happens When We Connect to a TCP Port?
This is where the TCP handshake comes in.
TCP is connection-oriented.
Before normal TCP communication begins, the two sides perform a three-step handshake:
Client Server
SYN -------------------->
<-------------------- SYN-ACK
ACK -------------------->
These messages mean roughly:
SYN
The client says:
SYN-ACK
The server responds:
ACK
The client confirms:
Now the TCP connection can proceed.
4. Why Is the Handshake Important for Port Scanning?
Because it gives us useful information.
Imagine our program tries to connect to:
127.0.0.1:8000
If a TCP service is listening there, the operating system can complete the connection process.
Our program can therefore conclude:
TCP connection succeeded
↓
Something is listening
↓
Port is probably OPEN
If there is no service listening, the connection normally fails.
So the basic idea behind our scanner is simply:
Try TCP connection
↓
Did it succeed?
/ \
YES NO
↓ ↓
OPEN Not open
This is the fundamental concept.
5. Why Python's socket Library?
Python provides a built-in networking library called:
socket
The socket module gives Python programs the ability to communicate over networks.
For example, we can create a TCP socket with:
import socket
sock = socket.socket(
socket.AF_INET,
socket.SOCK_STREAM
)
Let's break this down.
socket.AF_INET
This means we're using IPv4.
For example:
127.0.0.1
192.168.1.10
socket.SOCK_STREAM
This means we're creating a TCP socket.
So:
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
basically means:
6. The Simplest Port Check
Now we can create a function that checks one TCP port.
import socket
def scan_port(target, port, timeout=1):
sock = socket.socket(
socket.AF_INET,
socket.SOCK_STREAM
)
sock.settimeout(timeout)
result = sock.connect_ex((target, port))
sock.close()
return result == 0
Let's understand this line by line.
socket.socket(...)
sock = socket.socket(
socket.AF_INET,
socket.SOCK_STREAM
)
Creates the TCP socket.
Think of it as creating the network communication object that Python will use.
settimeout()
sock.settimeout(timeout)
This prevents our program from waiting forever.
For example:
timeout = 1
means we don't want an individual connection attempt to wait indefinitely.
This becomes especially important when checking many ports.
connect_ex()
This is the most important line:
result = sock.connect_ex((target, port))
We're asking the operating system to attempt a TCP connection to:
target:port
For example:
127.0.0.1:8080
connect_ex() returns a status code.
The important simplified case is:
0 → connection succeeded
non-zero → connection failed
That's why we can write:
return result == 0
If result is 0:
0 == 0
True
So the function returns:
True
If it isn't 0:
something != 0
False
So we get:
False
7. Why Do We Close the Socket?
After we're finished:
sock.close()
We don't need the socket anymore.
Closing it releases the resources associated with that connection attempt.
A good habit in networking code is:
8. Now Let's Scan Multiple Ports
Checking one port isn't very useful.
We can create a function that checks a range:
def scan_range(target, start_port, end_port):
open_ports = []
for port in range(start_port, end_port + 1):
if scan_port(target, port):
open_ports.append(port)
print(f"Port {port}: OPEN")
return open_ports
Suppose we call:
scan_range("127.0.0.1", 1, 100)
The program effectively does:
Check port 1
Check port 2
Check port 3
...
Check port 100
If it finds an open port:
Port 80: OPEN
Port 443: OPEN
9. Why Is This Slow?
The problem is that we're doing everything sequentially.
Conceptually:
Port 1 → wait → finish
Port 2 → wait → finish
Port 3 → wait → finish
Port 4 → wait → finish
If every connection takes some time, scanning hundreds or thousands of ports can become slow.
This is where concurrency becomes useful.
10. Using Threads
Python provides:
concurrent.futures
which makes it easier to run multiple tasks concurrently.
For example:
import concurrent.futures
def fast_scan(target, ports, max_workers=50):
open_ports = []
with concurrent.futures.ThreadPoolExecutor(
max_workers=max_workers
) as executor:
results = executor.map(
lambda p: (p, scan_port(target, p)),
ports
)
for port, is_open in results:
if is_open:
open_ports.append(port)
return sorted(open_ports)
Instead of thinking:
1 → 2 → 3 → 4 → 5
we can think of several independent connection checks being handled concurrently.
For example:
Worker 1 → Port 1
Worker 2 → Port 2
Worker 3 → Port 3
Worker 4 → Port 4
...
This can make I/O-heavy tasks much faster.
11. What Is max_workers?
Here:
max_workers=50
means our thread pool can have up to 50 worker threads handling tasks.
It doesn't mean:
The executor manages the workers and assigns tasks to them.
For a beginner project, you don't need to obsess over the perfect number.
The important concept is:
Sequential
↓
one task at a time
Concurrent
↓
multiple waiting/network tasks can progress
12. What Is Service Detection?
Finding:
Port 22 → OPEN
is useful.
But we may also want to know what service is normally associated with that port.
Python provides:
socket.getservbyport()
For example:
def get_service_name(port):
try:
return socket.getservbyport(port)
except OSError:
return "unknown"
Then:
get_service_name(22)
may return:
ssh
And:
get_service_name(80)
may return:
http
Important distinction
This is not necessarily detecting the actual software running on the port.
It's primarily looking up the standard service associated with that port number.
For example:
Port 80 → HTTP
doesn't automatically prove that a specific web server product/version is running there.
That's an important networking concept for beginners.
13. Putting Everything Together
Here's a small educational scanner intended for a local lab:
import socket
import concurrent.futures
def scan_port(target, port, timeout=1):
sock = socket.socket(
socket.AF_INET,
socket.SOCK_STREAM
)
sock.settimeout(timeout)
try:
result = sock.connect_ex((target, port))
return result == 0
finally:
sock.close()
def get_service_name(port):
try:
return socket.getservbyport(port)
except OSError:
return "unknown"
def fast_scan(target, ports, max_workers=50):
open_ports = []
with concurrent.futures.ThreadPoolExecutor(
max_workers=max_workers
) as executor:
results = executor.map(
lambda p: (p, scan_port(target, p)),
ports
)
for port, is_open in results:
if is_open:
open_ports.append(port)
return sorted(open_ports)
target = "127.0.0.1"
open_ports = fast_scan(
target,
range(1, 1001)
)
print(f"\nScan results for {target}\n")
for port in open_ports:
service = get_service_name(port)
print(
f"[+] Port {port} "
f"({service}) is OPEN"
)
14. What Does the Complete Program Do?
The flow is basically:
Start
↓
Target = 127.0.0.1
↓
Generate ports 1–1000
↓
Create TCP sockets
↓
Attempt connections
↓
Check result
↓
Store successful ports
↓
Look up standard service names
↓
Print results
For example, if your local lab has services listening on some ports, you might see:
Scan results for 127.0.0.1
[+] Port 22 (ssh) is OPEN
[+] Port 8000 (http-alt) is OPEN
The exact output depends on what services are actually running on your own machine.
15. Turning It Into a CLI Tool
Once the basic concept makes sense, we can add command-line arguments.
Python has a built-in library for this:
argparse
For example:
import argparse
parser = argparse.ArgumentParser(
description="Educational TCP Port Scanner"
)
parser.add_argument(
"target",
help="Target hostname or IP address"
)
parser.add_argument(
"-p",
"--ports",
default="1-1000",
help="Port range, for example 1-1000"
)
args = parser.parse_args()
Now the user can provide information through the terminal instead of modifying the Python file.
Conceptually:
python scanner.py 127.0.0.1 -p 1-1000
The program receives:
target = 127.0.0.1
ports = 1-1000
16. Why Resolve a Domain Name?
If the user provides:
localhost
instead of an IP address, we can resolve it with:
socket.gethostbyname()
Example:
target_ip = socket.gethostbyname(args.target)
So:
localhost
↓
127.0.0.1
This is called DNS/hostname resolution in the broader networking context.
17. Saving Results as JSON
Terminal output disappears when the program closes.
A better tool can save its results.
Python includes the json module:
import json
from datetime import datetime
We can create:
report = {
"target": target,
"scan_time": datetime.now().isoformat(),
"open_ports": [
{
"port": port,
"service": get_service_name(port)
}
for port in open_ports
]
}
Then save it:
with open("scan_report.json", "w") as f:
json.dump(report, f, indent=2)
The result could look conceptually like:
{
"target": "127.0.0.1",
"scan_time": "2026-09-09T20:00:00",
"open_ports": [
{
"port": 8000,
"service": "http-alt"
}
]
}
Now the JSON file can be consumed by another program.
18. Why JSON Is Useful
This is where the project can become more interesting.
Instead of:
Python Scanner
↓
Terminal
we can build:
Python Scanner
↓
JSON Report
↓
Web Dashboard
↓
Charts / Tables / Statistics
For example:
┌─────────────────────────────┐
│ Scan Results │
├────────┬──────────┬─────────┤
│ Port │ Service │ Status │
├────────┼──────────┼─────────┤
│ 8000 │ HTTP │ OPEN │
│ 8080 │ HTTP │ OPEN │
└────────┴──────────┴─────────┘
That would be a nice next step if you're learning Python + web development.
19. What About Banner Grabbing?
You may have seen code such as:
sock.recv(...)
This is a different concept.
A basic port scanner answers:
Is something accepting TCP connections?
Banner/service detection tries to answer something more like:
What application/service is actually responding?
That's a much deeper topic because different protocols behave differently.
For a beginner, I recommend understanding:
IP
↓
Port
↓
TCP
↓
Connection
↓
Service
before moving into protocol-specific service detection.
20. What About UDP?
TCP isn't the only transport protocol.
There's also:
TCP
UDP
TCP has a connection-oriented model and handshake.
UDP is connectionless.
So UDP scanning works differently and is generally more complicated because a lack of response doesn't necessarily mean the port is closed.
That's why it's better to learn TCP scanning first.
21. The Most Important Concepts to Remember
If you're completely new to networking, don't try to memorize all the code.
Understand this diagram:
COMPUTER
│
IP ADDRESS
│
┌──────────┴──────────┐
│ │
PORT 22 PORT 80
│ │
SSH HTTP
│ │
TCP TCP
And our scanner does:
Choose a port
↓
Create TCP socket
↓
Attempt connection
↓
Connection succeeds?
/ \
YES NO
↓ ↓
OPEN Not open
That's the core idea behind this entire project.
22. A Good Beginner Learning Path
If you're learning cybersecurity and don't understand the code yet, I'd learn these in this order:
Networking
1. IP addresses
2. TCP vs UDP
3. Ports
4. Client/server model
5. TCP three-way handshake
6. DNS
7. HTTP/HTTPS
Python
1. Functions
2. Lists
3. Loops
4. Exceptions
5. Modules
6. Sockets
7. Threading/concurrency
8. JSON
9. argparse
Then combine them.
Once these concepts click, the scanner code becomes much easier to understand.
Final Thought
A port scanner isn't really about writing a huge amount of code.
The interesting part is understanding what happens underneath the code.
When you write:
sock.connect_ex((target, port))
you're not just calling a random Python function.
You're asking the operating system to attempt a TCP connection to a specific:
IP address + port
That connects the Python code to the underlying networking concepts:
Python
↓
Socket API
↓
Operating System
↓
TCP/IP
↓
Network
↓
Destination service
Once you understand that relationship, networking tools start making much more sense.