LinuxIT-TCP-IP

Chapter 4: Command Handler System

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”).

The Problem: How Does the Server Know What to Do?

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.

The Solution: The Command Handler System

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:

This mechanism ensures that the correct business logic is executed efficiently for every incoming command, without mixing everything in one giant code block.

How Our App Uses the Command Handler System

Let’s trace how a command flows through our system and how the right handler is picked.

The Command Processing Flow

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.

Example: Handling a “Classify Image” vs. “Change Model” Command

  1. Client sends {"cmd": 0x01, "request_data": ["pear.jpg"]}
    • The Peer receives this.
    • The Peer asks the Command Handler System: “Which handler for 0x01?”
    • The system finds that 0x01 is handled by ClassificationHandler.
    • An instance of ClassificationHandler is created and given to the Peer.
    • The Peer tells ClassificationHandler to handle the request.
    • ClassificationHandler then uses the PearDetector (our AI expert) to process pear.jpg.
    • Results are sent back to the client.
  2. Client sends {"cmd": 0x26, "request_data": ["new_model.pt"]}
    • The Peer receives this.
    • The Peer asks the Command Handler System: “Which handler for 0x26?”
    • The system finds that 0x26 is handled by ModelHandler.
    • An instance of ModelHandler is created and given to the Peer.
    • The Peer tells ModelHandler to handle the request.
    • ModelHandler then changes the active PearDetector’s model to new_model.pt.
    • Confirmation is sent back to the client.

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!

Under the Hood: The Map and the Specialists

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.

1. The Command Map: 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)

2. The Blueprint for All Specialists: 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
        }

3. A Specific Specialist: 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

4. Another Specific Specialist: 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)])

5. Bringing it all together in 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:

  1. It receives and processes the incoming message into a Python dictionary.
  2. It uses 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.
  3. It then calls await handler.handle(request) to tell that specific specialist to do its work.
  4. Finally, it takes the results from the handler and sends them back to the client.

Why the Command Handler System is Great

Having a Command Handler System provides many benefits:

Conclusion

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.