Building a Python Socket Program: TCP Client and Server Fundamentals
Overview
Building a Python socket program is one of the best ways to understand what is actually happening when two systems communicate across a network. Instead of relying on tools such as Netcat, Nmap, or a web browser, Python allows us to create the network connection ourselves and see how data moves between a client and server.
Python includes the socket module in its standard library, so no additional packages are required. With only a few lines of code, we can create a TCP server that listens for connections and a TCP client that connects to it, sends information, and receives a response.
This technique provides a foundation for understanding port scanners, network services, banner grabbing, custom protocol clients, automation, and many of the Python scripts commonly encountered during cybersecurity labs.
Lab Note: Run these examples only on systems you own or have permission to use. The easiest setup is to run both programs on the same computer using
127.0.0.1.
What Is a Network Socket?
A socket is a software endpoint used by applications to communicate across a network.
A TCP connection can be thought of as communication between two endpoints:
Client Server
192.168.1.20 192.168.1.50
Random Source Port TCP Port 5000
| |
| -------- TCP Connection ---> |
| |
| -------- "Hello" ----------> |
| |
| <------- "Received" -------- |
The server listens for incoming connections on a specific port. The client knows the server’s IP address and port and attempts to establish a connection.
Once TCP establishes the connection, both systems can send and receive data.
Why Python Sockets Matter in Cybersecurity
Many security tools ultimately interact with network sockets.
When you run:
ssh server.example.com
the SSH client establishes a TCP connection to the remote server.
When you visit:
https://example.com
your browser establishes network connections to the web server.
When a port scanner determines whether a TCP service is accessible, it is also interacting with network sockets.
Learning Python socket programming removes some of the abstraction and lets us work directly with those connections.
Importing the Socket Module
Python provides socket functionality through:
import socket
Because socket is part of Python’s standard library, there is normally nothing additional to install.
We can now create a socket with:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Two important values appear here.
| Value | Purpose |
|---|---|
AF_INET | Use IPv4 addresses |
SOCK_STREAM | Use TCP |
Therefore:
socket.AF_INET
means we want IPv4 networking, while:
socket.SOCK_STREAM
means we want a TCP connection.
Building a Simple Python TCP Server
Let’s start with the server.
Create:
server.py
and add:
#!/usr/bin/env python3
import socket
HOST = "127.0.0.1"
PORT = 5000
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen(1)
print(f"Listening on {HOST}:{PORT}")
connection, address = server.accept()
print(f"Connection received from {address}")
data = connection.recv(1024)
print(f"Received: {data.decode()}")
connection.sendall(b"Message received by server")
connection.close()
server.close()
Run it with:
python3 server.py
The terminal should display:
Listening on 127.0.0.1:5000
At this point, the program appears to stop.
It hasn’t.
The server is waiting for another program to connect.
Understanding bind()
This line determines where the server listens:
server.bind((HOST, PORT))
Our variables contain:
HOST = "127.0.0.1"
PORT = 5000
The address:
127.0.0.1
is the IPv4 loopback address.
That means the service is available only from the local computer, making it ideal for experimenting safely.
The server therefore listens on:
127.0.0.1:5000
Understanding listen()
Next we use:
server.listen(1)
This places the socket into listening mode so that it can accept incoming TCP connections.
The argument controls the connection backlog rather than defining how many total clients the server can ever serve.
Our simple example handles only one connection before terminating, but a real network server would normally continue accepting additional clients.
Understanding accept()
The next important operation is:
connection, address = server.accept()
This waits until a client establishes a connection.
When that happens, Python returns two useful pieces of information:
connection
address
connection represents the newly established client connection.
address identifies the remote client.
For example:
('127.0.0.1', 52134)
The second number is typically an ephemeral source port selected for the client connection.
Building the Python TCP Client
Now we need something to connect to the server.
Create another file:
client.py
Add:
#!/usr/bin/env python3
import socket
HOST = "127.0.0.1"
PORT = 5000
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOST, PORT))
client.sendall(b"Hello from the Python client")
response = client.recv(1024)
print(f"Server response: {response.decode()}")
client.close()
Keep server.py running and open another terminal.
Run:
python3 client.py
The client should connect to TCP port 5000 and send:
Hello from the Python client
The server receives the message and responds:
Message received by server
We have now created basic network communication entirely in Python.
Understanding connect()
The client establishes the connection with:
client.connect((HOST, PORT))
This tells the operating system to establish a TCP connection with:
127.0.0.1:5000
Conceptually:
client.py
|
| connect()
v
127.0.0.1:5000
|
v
server.py
If nothing is listening on port 5000, the connection will fail.
That same principle is one of the foundations of TCP port scanning.
Sending Data with sendall()
The client sends information using:
client.sendall(b"Hello from the Python client")
Notice the b before the string.
b"Hello"
represents bytes rather than a normal Python string.
Network sockets transmit bytes.
If we had a normal string:
message = "Hello"
we could convert it using:
message.encode()
For example:
client.sendall(message.encode())
Receiving Data with recv()
The server receives information with:
data = connection.recv(1024)
The value:
1024
specifies the maximum number of bytes requested by that particular recv() call.
The returned data is still bytes.
That is why the example uses:
data.decode()
to convert the received bytes into a Python string for display.
The basic process becomes:
Python String
|
encode()
|
v
Bytes
|
Network
|
v
Bytes
|
decode()
|
v
Python String
Understanding this distinction becomes especially important when working with binary network protocols.
Watching the Connection with ss
While experimenting with the server, we can use Linux networking tools to verify that the service exists.
Start:
python3 server.py
Then open another terminal and run:
ss -lnt
You should see a listening TCP socket associated with port 5000.
You can narrow the output with:
ss -lnt | grep 5000
This connects the Python programming concept with what the operating system actually sees: our script has created a real listening TCP service.
Testing the Python Server with Netcat
One of the useful things about network protocols is that both sides do not necessarily need to use the same application.
Instead of client.py, try connecting with Netcat:
nc 127.0.0.1 5000
Enter:
Hello from Netcat
The Python server receives those bytes just as it received data from our Python client.
This demonstrates an important networking concept.
The server doesn’t care whether the connection came from our custom Python script or Netcat. It sees a TCP connection carrying data.
From Socket Programming to Port Scanning
The same concepts can be extended into a basic TCP port scanner.
Instead of connecting to only:
127.0.0.1:5000
a scanner attempts connections to multiple ports:
127.0.0.1:21
127.0.0.1:22
127.0.0.1:80
127.0.0.1:443
127.0.0.1:5000
If a TCP connection succeeds, something is listening on that port.
This is exactly why understanding sockets before studying the Python Port Scanner is useful. The scanner is simply automating the networking concepts demonstrated here.
From Socket Programming to Modbus TCP
Socket programming also becomes much more interesting when working with protocols other than simple text.
In the TryHackMe Brr room, for example, Python sockets can communicate directly with a PLC using Modbus TCP.
Instead of sending:
b"Hello"
the program constructs binary data representing a valid Modbus request.
The basic networking process remains the same:
Create Socket
|
Connect to Server
|
Construct Protocol Request
|
Send Bytes
|
Receive Bytes
|
Parse Response
The major difference is understanding how the particular protocol structures its data.
This is why learning basic socket programming is so valuable. Once the connection itself makes sense, protocols such as HTTP, SMTP, FTP, and Modbus become easier to investigate.
Security Considerations
Socket programming is powerful because Python allows us to communicate directly with network services. That also means scripts need appropriate safeguards and error handling.
A production program should consider:
- Connection timeouts
- Input validation
- Authentication
- Encryption
- Exception handling
- Resource cleanup
- Maximum message sizes
- Logging
- Multiple simultaneous clients
Our example intentionally leaves most of these features out so the underlying TCP communication remains easy to understand.
It should be considered an educational example rather than a production network service.
Common Mistakes
One common mistake when first working with sockets is confusing strings and bytes.
This will not work as expected:
client.sendall("Hello")
Instead, send bytes:
client.sendall(b"Hello")
or encode the string:
client.sendall("Hello".encode())
Another common problem is starting the client before the server. If nothing is listening on the destination port, the TCP connection cannot be established.
Finally, remember that:
recv(1024)
does not guarantee that an entire application-level message will arrive in one call. TCP is a byte-stream protocol. More sophisticated applications need a way to determine where messages begin and end, such as fixed lengths, delimiters, or length fields.
Where This Technique Leads
Once basic Python socket programming makes sense, it becomes the foundation for several useful cybersecurity projects:
- TCP port scanners
- Banner grabbers
- Simple chat applications
- HTTP clients
- Protocol analysis tools
- Network monitoring utilities
- Custom lab services
- Modbus TCP clients
- Automation scripts
The code changes, but the underlying process remains remarkably similar.
Key Takeaways
Building a Python socket program provides a practical way to understand TCP communication instead of treating networking as something hidden behind security tools.
The server follows the basic sequence:
socket()
↓
bind()
↓
listen()
↓
accept()
↓
recv()
↓
sendall()
The client follows:
socket()
↓
connect()
↓
sendall()
↓
recv()
The most important lesson is that tools such as port scanners and protocol clients are ultimately performing variations of these same fundamental network operations.
Important Python Methods
| Method | Purpose |
|---|---|
socket() | Creates the socket |
bind() | Associates a server with an address and port |
listen() | Places a TCP socket into listening mode |
accept() | Accepts an incoming connection |
connect() | Establishes a connection to a server |
sendall() | Sends bytes through the connection |
recv() | Receives bytes |
close() | Closes the socket |
Skills Practiced
- Python programming
- TCP/IP fundamentals
- Client/server architecture
- Socket programming
- TCP ports
- Sending and receiving network data
- String encoding and decoding
- Network troubleshooting
- Understanding the foundations of security tools
From a defensive perspective, understanding sockets also makes it easier to understand what network monitoring tools are showing you. A connection in a firewall, IDS, EDR, or SIEM log is no longer just an IP address and port—it represents applications creating sockets, establishing connections, and exchanging data across the network.
Why not build a python scanner to play with Sockets
