EZ LearnEZ Learn
Courses
EZ Board
Dashboard
All articles
python-for-cyber-securitypythonsocketsthreading

A Reusable Socket Server Framework

A fixed accepting loop plus an interchangeable handler is how most real network servers are built. The Morris Worm shows what happens when a service trusts whatever arrives on its socket.

Ashish Revar17 October 202613 min read
1 video lesson 5:19 total
Sign in to track your progress

Video lesson

The echo server in the sockets article handles one caller at a time, then loops back for the next. That is fine for learning. But the moment a second caller arrives while the first is still being served, the second has to wait. This article fixes that, and in doing so gives you a design you will recognise in almost every real network server.

The problem: one caller at a time

Take the single-loop echo server and make each caller need two seconds of "work". Three clients arrive together:

sequential_slow.py

# the single-loop echo server, made slow: one caller at a time
import socket, time

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 9998))
server.listen(5)
while True:
    client_sock, client_addr = server.accept()
    data = client_sock.recv(1024)
    time.sleep(2)                     # two seconds of "work" per caller
    client_sock.sendall(data)
    client_sock.close()

Three clients arrive at the same moment

client 1: got b'hello 1' after 2.0s
client 2: got b'hello 2' after 4.0s
client 3: got b'hello 3' after 6.0s
all three finished in 6.0s

The third caller waited six seconds for a two-second job. A slow or silent caller at the front of the queue stalls everyone behind it.

The idea: separate accepting from handling

A more reusable design separates the part that accepts connections from the part that decides what to do with each one. Then the same accepting loop can serve a banner-collecting listener, a small honeypot, or a logging service without the networking code being rewritten each time.

Analogy: The front desk and the bellboys

Picture a small hotel. If the receptionist had to carry each guest's bags up to the room before greeting the next guest, a queue would build at the desk. So she does not. She greets the guest, hands the bags to a bellboy, and turns straight back to the next arrival.

The receptionist is the accept loop. Each bellboy is a thread. What the bellboy actually does with the guest, the room, the tea, the luggage, is the handler. Change the handler and the same hotel runs a different service.

Accept loopserver.accept()then straight back to waitThread for caller Ahandler(sock, addr)finally: close()Thread for caller Bhandler(sock, addr)finally: close()Thread for caller Chandler(sock, addr)finally: close()
Figure 13.1. A fixed accepting loop hands every connection to its own thread. Each thread runs the handler you supplied and always closes the connection afterwards.

The framework

server_framework.py

# server_framework.py: a reusable threaded socket server
import socket
import threading

class SocketServerFramework:
    def __init__(self, host, port, handler):
        self.host = host
        self.port = port
        self.handler = handler      # a function that takes one connected socket

    def start(self):
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((self.host, self.port))
        server.listen(5)
        print(f"Framework listening on {self.host}:{self.port}")
        while True:
            client_sock, client_addr = server.accept()
            thread = threading.Thread(
                target=self._handle_client, args=(client_sock, client_addr)
            )
            thread.daemon = True
            thread.start()

    def _handle_client(self, client_sock, client_addr):
        try:
            self.handler(client_sock, client_addr)
        finally:
            client_sock.close()

def log_and_echo(client_sock, client_addr):
    print(f"New connection from {client_addr}")
    data = client_sock.recv(1024)
    print(f"Received: {data!r}")
    client_sock.sendall(data)

if __name__ == "__main__":
    server = SocketServerFramework("0.0.0.0", 9999, log_and_echo)
    server.start()
PieceWhat it does
class SocketServerFrameworkA class (you met the rubber stamp earlier) that bundles a host, a port and a handler
handlerA function passed in from outside. The framework never knows what it does; it only calls it
server.accept()Wait for the next caller. The receptionist's job
threading.Thread(target=..., args=...)Create a bellboy: a separate line of execution that will run _handle_client for this one caller
thread.daemon = TrueA daemon thread will not keep the program alive on its own. When you stop the server, stray threads do not hold it open
thread.start()Send the bellboy off. accept() is free again immediately
try: ... finally: client_sock.close()Whatever the handler does, including crashing, the connection is closed. Cleanup always lives in finally
log_and_echoThe simplest handler: log, read, echo

Python concept: Threads

Handing each connection to its own threading.Thread lets the server go straight back to accept() instead of waiting for one slow caller to finish before serving the next. For a lab server handling a few connections at once, threads are enough.

A production listener expecting thousands of simultaneous callers would move to Python's asyncio framework, which is outside this course's scope.

Run the framework in one terminal and send it two messages from another:

Server terminal

Framework listening on 0.0.0.0:9999
New connection from ('127.0.0.1', 64328)
Received: b'hello'
New connection from ('127.0.0.1', 64329)
Received: b'second client'

Change the handler, change the service

The handler is passed in from outside, so the same framework becomes a different service simply by supplying a different function. This pattern, a fixed accepting loop plus an interchangeable handler, is how most real network servers are structured, and you will recognise it again whenever you read the source of one.

Handler 1: a slow echo, to prove the threads work

Run the same three clients against the framework with a handler that also takes two seconds:

handlers.py

# handlers.py: different services built on the same framework
import time
from server_framework import SocketServerFramework

def slow_echo(client_sock, client_addr):
    # pretend each caller needs two seconds of work
    data = client_sock.recv(1024)
    time.sleep(2)
    client_sock.sendall(data)

The same three clients, now served in parallel

client 3: got b'hello 3' after 2.0s
client 1: got b'hello 1' after 2.0s
client 2: got b'hello 2' after 2.0s
all three finished in 2.0s

Six seconds became two. All three callers were served at once, and they finished in whatever order the threads happened to complete. That is the whole point of the bellboys.

Handler 2: a small honeypot

A honeypot is a service whose only job is to be knocked on. This handler announces a believable banner, listens briefly, and logs who came by and what they sent. It uses the banner from the previous article, from the other side of the conversation:

honeypot handler

def banner_honeypot(client_sock, client_addr):
    # announce a banner, listen briefly, and log who knocked
    client_sock.sendall(b"SSH-2.0-OpenSSH_7.4\r\n")
    client_sock.settimeout(1.0)
    try:
        first = client_sock.recv(1024)
    except OSError:
        first = b""
    print(f"HONEYPOT: {client_addr[0]} knocked, sent {first!r}")

SocketServerFramework("127.0.0.1", 9997, banner_honeypot).start()

Point grab_banner() from the previous article at port 9997, then knock once more with a client that introduces itself. The honeypot's terminal shows both visitors:

Honeypot terminal

Framework listening on 127.0.0.1:9997
HONEYPOT: 127.0.0.1 knocked, sent b''
HONEYPOT: 127.0.0.1 knocked, sent b'SSH-2.0-scanner_1.0\r\n'

The first line is grab_banner(): it connected, read the banner, and hung up without sending a word. The second is a client that answered with its own greeting. A scanner and a real SSH client leave different footprints, and a honeypot's log is how a defender tells them apart.

Handler 3: never wait forever

Real callers are slow, confused or hostile. A caller that connects and says nothing would hold its thread open indefinitely. A handler should always set a timeout.

hardened.py

import socket
from server_framework import SocketServerFramework

def safe_echo(client_sock, client_addr):
    client_sock.settimeout(2.0)            # never wait forever for a caller
    try:
        data = client_sock.recv(1024)
    except socket.timeout:
        print(f"{client_addr[0]} connected but said nothing; hanging up")
        return
    client_sock.sendall(data)

SocketServerFramework("127.0.0.1", 9996, safe_echo).start()

A caller that connects and stays silent

silent caller was disconnected after 2.0 s; recv gave b''

Framework listening on 127.0.0.1:9996
127.0.0.1 connected but said nothing; hanging up

After two seconds the server gave up on the silent caller, closed the connection through the finally block, and freed the thread. Without that timeout, a few hundred silent callers could tie up every thread the server had.

Close in finally

Every connection is closed whatever the handler did, so a crash never leaks a socket.

Set a timeout

A caller who says nothing cannot hold a thread forever.

Limit what you read

recv(1024) takes at most 1024 bytes at once. Never assume what arrives is small, well-formed or polite.

Log who knocked

The address and the first bytes are the evidence. Without a log, an attack leaves nothing to study.

Caution: Lab use only

A listener is a door. Run these servers on 127.0.0.1 for practice, and only ever on 0.0.0.0 inside an isolated lab. A honeypot exposed to the open internet will attract real attackers, and operating one on a network you do not control can breach your institution's policy.

A case from 1988

Real incident: The Morris Worm, 1988: one unchecked service, ten percent of the internet

In November 1988, a program written by Robert Tappan Morris, then a graduate student at Cornell University, spread across the early internet by exploiting weaknesses in a few standard network services, among them a buffer overflow in the fingerd daemon and guessable passwords tried over the rsh and rexec services.

The worm was not built to destroy data, but a flaw in its own spreading logic let it reinfect a machine repeatedly, and roughly ten percent of the internet's machines at the time, an estimated six thousand systems, slowed to a crawl or stopped responding. Morris became the first person convicted under the United States' then-new Computer Fraud and Abuse Act.

What this teaches: every weakness the worm used travelled over an ordinary socket to an ordinary network service, the same TCP building block this unit has just covered. Four decades later the lesson has not changed: a network service that accepts a connection without carefully checking what arrives on it remains one of the oldest and most reliable ways into a system. That is precisely why the framework above closes every connection in a finally block, why a good handler sets timeouts and limits what it reads, and why Unit 2 treats every byte a service returns as untrusted.

Check your understanding

Question 1: What are the two parts of the framework, and why is it useful to keep them separate?

The accept loop (waiting for callers and handing each to a thread) and the handler (the function that decides what to do with one connection). Keeping them separate means the same networking code can serve a completely different service just by passing in a different handler.

Question 2: Why does each connection get its own thread?

So the server can go straight back to accept() instead of waiting for one slow caller. With a single loop, three two-second callers took six seconds in total; with a thread each, all three finished in two.

Question 3: Why does _handle_client close the socket in a finally block?

Because finally runs whether the handler finished normally or crashed. Closing there guarantees the connection is always released, so a faulty handler cannot leak sockets.

Question 4: A caller connects to your handler and never sends anything. What happens without a timeout, and what is the fix?

The handler waits on recv() forever, holding its thread. Enough silent callers would exhaust the server. The fix is client_sock.settimeout(...) and a try/except socket.timeout that hangs up.

Key takeaway: Key takeaway

A fixed accepting loop plus an interchangeable handler is how most real network servers are built. Give each caller its own thread, close every connection in finally, set timeouts, and treat everything that arrives as untrusted. The Morris Worm showed in 1988 what happens when a service does not.

Assessments

Unit Assessment

Python and Networking Foundations

20 questions30 minPass: 60%

Start exam →

Comprehensive Assessment

Python for Cyber Security: Complete Assessment

100 questions120 minPass: 60%

Start final exam →

Related to this topic

Continue learning

Reference material

Cheatsheet

Python for Cyber Security — Cheatsheet

v1.0

Open resource

MCQ Bank

Python for Cyber Security — MCQ Bank

v1.0

Open resource

Question Bank

Python for Cyber Security — Question Bank

v1.0

Open resource

eBook

Python for Cyber Security — eBook

v1.0

Open resource

Discussion

Loading comments…
More articlesTest your knowledge
EZ MentorAI · Free

A Reusable Socket Server Framework

Hi! I'm EZ Mentor

Your AI tutor for
A Reusable Socket Server Framework

Sign in to ask, it's free.

EZ Mentor is AI-powered · Answers may not be perfect · 10 free questions / day

EZ LearnEZ Learn

Structured learning across Reverse Engineering, Cloud Security, and Research Methodology, plus games and practice tools in EZ Arena. Free, and open to anyone.

EZ Learn

  • Courses
  • EZ Articles
  • EZ Watch
  • EZ Talks
  • EZ Bytes
  • Cyber Hygiene

EZ Library

  • All Resources
  • eBooks & PDFs
  • Question Banks
  • MCQ Banks
  • Cheatsheets

EZ Exams

  • All Exams
  • REMA
  • Cloud Security
  • Research Methodology
  • Python for Cyber Security
  • Cyber Law & IPR
  • Community
  • EZ Board

EZ Arena

  • EZ Games
  • Live Quiz
  • ML Challenges
  • दिशा - Hygiene
  • सजग - Awareness
  • अभ्यास - Practice

Platform

  • About
  • Contact
  • Terms of Use
  • Disclaimer
  • Verify Certificate
  • Events

© 2026 EZ Learn by EpochZero. Educational content for learning purposes.

Platform Lead: Ashish Revar