Welcome back! In Chapter 1: Application Configuration, we learned how our application loads its settings, like server ports or paths to AI models. Then, in Chapter 2: Communication Protocol, we explored how our server and clients “speak” the same language using structured messages. Most recently, in Chapter 3: Pear Detection Model (PearDetector), we saw how our “AI expert” processes images to detect pears.
Now, imagine our server receives a message from a client. This message, as we know from Chapter 2, contains a "cmd"
(command code) field. This cmd
could be 0x01
(meaning “classify an image”), or 0x26
(meaning “change the active AI model”), or 0x32
(meaning “tell me the current image folder”).
Our LinuxIT-TCP-IP server is not just a pear detection machine; it’s a versatile service. It can handle many different kinds of requests:
If our server had to write a giant if/else if/else
statement for every single possible command, like this:
# Imagine if our server looked like this... (Very BAD!)
if command == 0x01:
# Do image classification logic here (too much code!)
elif command == 0x26:
# Do model change logic here (more too much code!)
elif command == 0x32:
# Do image folder logic here (even more too much code!)
# ... and so on for every single command!
This approach would quickly become a mess!
We need a better, more organized way to handle different client requests.
This is where the Command Handler System comes in! Think of it like a sophisticated dispatcher or the information desk at a large department store.
When a customer (our client) comes in with a request (a “command” with its cmd
code), the information desk (the Command Handler System) doesn’t try to fulfill all requests itself. Instead, it looks at the request type and directs the customer to the right specialist department (a “handler”) that is specifically equipped to deal with that request.
Here’s how it works in our project:
cmd
codes from our Communication Protocol (like 0x01
for classification, 0x26
for model change).ClassificationHandler
, ModelHandler
, DirectoryHandler
). Each handler knows how to deal with one or more related types of commands. For example:
ClassificationHandler
deals with 0x01
(classify image).ModelHandler
deals with 0x22
(get current model), 0x24
(list models), 0x26
(change model), and 0x28
(delete model).cmd
code to its correct handler
class.This mechanism ensures that the correct business logic is executed efficiently for every incoming command, without mixing everything in one giant code block.
Let’s trace how a command flows through our system and how the right handler is picked.
sequenceDiagram
participant Client
participant Peer (Connection Handler)
participant Command Handler System
participant Specific Handler (e.g., ClassificationHandler)
participant PearDetector (AI Expert)
Client->>Peer (Connection Handler): Sends message (e.g., {"cmd": 0x01, "request_data": ["image.jpg"]})
Peer (Connection Handler)->>Peer (Connection Handler): Processes raw message (Chapter 2)
Peer (Connection Handler)->>Command Handler System: "Hey, I received command 0x01! Who handles this?"
Command Handler System->>Command Handler System: Looks up 0x01 in its map
Command Handler System-->>Peer (Connection Handler): "Ah, the ClassificationHandler handles 0x01. Here's an instance of it."
Peer (Connection Handler)->>Specific Handler (e.g., ClassificationHandler): "Okay, ClassificationHandler, handle this request!"
Specific Handler (e.g., ClassificationHandler)->>PearDetector (AI Expert): (If needed) "PearDetector, classify 'image.jpg'!" (Chapter 3)
PearDetector (AI Expert)-->>Specific Handler (e.g., ClassificationHandler): Returns classification result
Specific Handler (e.g., ClassificationHandler)-->>Peer (Connection Handler): Returns structured response data
Peer (Connection Handler)->>Peer (Connection Handler): Formats response message (Chapter 2)
Peer (Connection Handler)-->>Client: Sends formatted response
Notice how the Command Handler System
acts as the central point that directs the Peer
(the connection handler from Chapter 5: Client Connection Handler (Peer)) to the right Specific Handler
.
{"cmd": 0x01, "request_data": ["pear.jpg"]}
Peer
receives this.Peer
asks the Command Handler System
: “Which handler for 0x01
?”0x01
is handled by ClassificationHandler
.ClassificationHandler
is created and given to the Peer
.Peer
tells ClassificationHandler
to handle
the request.ClassificationHandler
then uses the PearDetector
(our AI expert) to process pear.jpg
.{"cmd": 0x26, "request_data": ["new_model.pt"]}
Peer
receives this.Peer
asks the Command Handler System
: “Which handler for 0x26
?”0x26
is handled by ModelHandler
.ModelHandler
is created and given to the Peer
.Peer
tells ModelHandler
to handle
the request.ModelHandler
then changes the active PearDetector
’s model to new_model.pt
.This way, the Peer
doesn’t need to know the details of how to classify an image or how to change a model; it just knows who to ask!
Let’s look at the actual code that makes this system work. All the core parts of the Command Handler System are located in the src/handlers/
directory.
src/handlers/__init__.py
This file is like the central “information desk” that holds the map connecting command codes to their respective handlers.
# src/handlers/__init__.py (simplified)
from .classification_handler import ClassificationHandler
from .model_handler import ModelHandler
from .directory_handler import DirectoryHandler
# ... other handlers ...
# This is our central map!
HANDLER_MAPPING = {
0x01: ClassificationHandler, # request_classification
0x03: ClassificationHandler, # stop_classification
0x20: DownloadHandler, # request_download
0x22: ModelHandler, # request_current_model
0x24: ModelHandler, # request_list_model
0x26: ModelHandler, # model_change_request
0x28: ModelHandler, # request_delete_model
0x30: DirectoryHandler, # request_change_img_folder
0x32: DirectoryHandler # request_current_img_folder
# ... many more mappings
}
def get_handler(command_code: int, model: 'PearDetector') -> 'BaseHandler':
"""
Get appropriate handler for a command code.
"""
handler_class = HANDLER_MAPPING.get(command_code)
if not handler_class:
raise ValueError(f"Unknown command code: {hex(command_code)}")
# Create and return an instance of the specific handler
return handler_class(model, command_code)
HANDLER_MAPPING
: This is the core dictionary that stores our “map.” The keys are the command codes (like 0x01
, 0x26
), and the values are the actual Python class
es (like ClassificationHandler
, ModelHandler
) that can handle those commands.get_handler(command_code, model)
: This is the important function that the Peer
calls. You give it a command_code
, and it looks up that code in HANDLER_MAPPING
. If it finds a match, it creates a new “specialist” (an instance of the handler class) and returns it. It also passes the PearDetector
model to the handler, so the specialist can use our AI expert if needed.src/handlers/base_handler.py
All our specialized handlers (like ClassificationHandler
and ModelHandler
) share some common features. They all need a way to handle
a request and a way to create_response
. We define these common features in a BaseHandler
class, which acts like a blueprint.
# src/handlers/base_handler.py (simplified)
from abc import ABC, abstractmethod
from typing import Dict, Any, List
class BaseHandler(ABC): # ABC means "Abstract Base Class" - it's a blueprint!
def __init__(self, model: 'PearDetector', command_code: int):
self.model = model # Our AI expert (PearDetector)
self.command_code = command_code
# Get the correct response code for this command from the global map
from . import RESPONSE_CODES
self.response_code = RESPONSE_CODES.get(command_code)
@abstractmethod # This means every handler MUST implement this method!
async def handle(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Handle the request and return appropriate response"""
pass # This is just a placeholder, real handlers do the work here
def create_response(self, response_data: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Create a properly formatted response dictionary"""
return {
"cmd": self.response_code, # Use the response code from Chapter 2
"response_data": response_data, # The actual results
"request_data": None # Request data is null for responses
}
BaseHandler(ABC)
: This tells Python that BaseHandler
is not meant to be used directly, but rather as a template for other handlers.__init__
: Every handler needs access to the PearDetector
(our AI expert) if it needs to perform image processing. It also stores the command_code
it’s handling and automatically figures out the correct response_code
(from Chapter 2) for messages it sends back.@abstractmethod async def handle(...)
: This is key! It forces every handler that uses this blueprint to have a handle
method. This handle
method is where the specific logic for that command type goes.create_response
: This is a helpful tool for all handlers to build responses that follow our Communication Protocol.src/handlers/classification_handler.py
This handler is specialized in classifying images. It “inherits” (uses the blueprint of) BaseHandler
.
# src/handlers/classification_handler.py (simplified)
import logging
from typing import Dict, Any, List
from .base_handler import BaseHandler, ResponseData
logger = logging.getLogger(__name__)
class ClassificationHandler(BaseHandler):
async def handle(self, request: Dict[str, Any]) -> Dict[str, Any]:
# This handler knows it deals with 0x01 (classification)
if self.command_code == 0x01:
return await self._handle_classification(request)
# It can also handle 0x03 (stop classification) if needed
elif self.command_code == 0x03:
return await self._handle_stop()
else:
raise ValueError(f"Invalid command code for ClassificationHandler: {hex(self.command_code)}")
async def _handle_classification(self, request: Dict[str, Any]) -> Dict[str, Any]:
response_data = []
files = request.get("request_data", []) # Get the list of files from the request
for file_name in files:
# Here, the ClassificationHandler uses the PearDetector (our AI expert)
result = await self.model.inference(file_name) # Call the AI expert (Chapter 3)
# Process the result from the PearDetector
if result.is_normal == 1:
response_data.append({"file_name": file_name, "result": "normal_pear", "error_code": 0})
elif result.is_normal == 0:
response_data.append({"file_name": file_name, "result": "defected_pear", "error_code": 0})
else:
response_data.append({"file_name": file_name, "result": "no_pear_found", "error_code": 0})
return self.create_response(response_data) # Use the inherited method to format response
handle(self, request)
: This method is where the ClassificationHandler
decides what to do based on the exact command code it received (0x01
or 0x03
)._handle_classification
: This is the actual logic for classifying images. Notice the crucial line: await self.model.inference(file_name)
. This shows how the handler interacts with the PearDetector
(which was passed to it in BaseHandler
’s __init__
). It then builds the response_data
and uses self.create_response
to send it back.src/handlers/model_handler.py
This handler specializes in anything related to managing AI models.
# src/handlers/model_handler.py (simplified)
import logging
from typing import Dict, Any
import os
from .base_handler import BaseHandler, ResponseData
logger = logging.getLogger(__name__)
class ModelHandler(BaseHandler):
async def handle(self, request: Dict[str, Any]) -> Dict[str, Any]:
# This handler can manage many model-related commands!
command_handlers = {
0x22: self._handle_current_model, # Get current model
0x24: self._handle_list_model, # List all models
0x26: self._handle_model_change, # Change active model
0x28: self._handle_delete_model # Delete a model
}
# It picks the right internal function based on the command code
handler = command_handlers.get(self.command_code)
if not handler:
raise ValueError(f"Invalid command code for ModelHandler: {hex(self.command_code)}")
return await handler(request)
async def _handle_current_model(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Get the name of the currently active model."""
# This handler accesses the PearDetector's configuration to know the current model
current_model_name = self.model.config.model_path.split("/")[-1]
response = ResponseData(file_name=None, result=current_model_name, error_code=0)
return self.create_response([vars(response)])
async def _handle_model_change(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Change the active model."""
model_name = request["request_data"][0]
# This handler tells the PearDetector to change its brain!
await self.model.change_model(model_name)
response = ResponseData(file_name=model_name, result="changed", error_code=0)
return self.create_response([vars(response)])
handle
: Similar to ClassificationHandler
, it uses its command_code
to call the correct internal function._handle_current_model
: Shows how it gets information about the current model from self.model.config
._handle_model_change
: Shows how it can actually change the PearDetector
’s behavior by calling self.model.change_model()
.src/server/peer.py
Finally, let’s see how the Peer
(our connection handler that listens for client messages, from Chapter 2) uses this Command Handler System.
# src/server/peer.py (simplified)
import asyncio
import json
import logging
from ..handlers import get_handler # The function that gets our specialists!
class Peer:
def __init__(self, reader, writer, config, detector):
self.reader = reader
self.writer = writer
self.config = config
self.detector = detector # Our PearDetector (AI expert)
# ... other initializations
async def handle_connection(self) -> None:
while True:
data = await self.reader.read(self.config.buffer_size)
if not data: break
try:
# 1. Process the raw incoming message (as seen in Chapter 2)
request = self.request_processor.process_request(data, self.config.encoding)
# 2. ASK THE COMMAND HANDLER SYSTEM FOR THE RIGHT SPECIALIST!
# get_handler returns an instance of ClassificationHandler, ModelHandler, etc.
handler = get_handler(request["cmd"], self.detector)
# 3. Tell the specialist to handle the request
response_dict = await handler.handle(request)
# 4. Send the formatted response back to the client (as seen in Chapter 2)
await self._send_response(response_dict)
except Exception as e:
# ... error handling
This snippet shows the core loop within the Peer
class:
get_handler(request["cmd"], self.detector)
to ask the Command Handler System for the correct “specialist” (handler
) for the received command (request["cmd"]
). It also gives the PearDetector
to the handler so it can use it.await handler.handle(request)
to tell that specific specialist to do its work.Having a Command Handler System provides many benefits:
HANDLER_MAPPING
. You don’t need to touch existing code.classification_handler.py
.In this chapter, we’ve learned about the Command Handler System, which acts like a smart dispatcher for our LinuxIT-TCP-IP server. Instead of having one giant piece of code for all commands, it uses a map (HANDLER_MAPPING
) to direct each incoming client request (based on its cmd
code) to the correct, specialized “handler.” This makes our application highly organized, easy to extend, and much more robust.
Now that our server knows how to wisely distribute incoming requests to the right specialists, how does it manage the actual connections from multiple clients? In the next chapter, we’ll dive into the Client Connection Handler (Peer), which manages individual client conversations.