TCP Server and Client Examples

Real-world examples of using our TCP implementation

Introduction

In this notebook, we’ll see practical examples of using our TCP server and client implementations. We’ll demonstrate:

  1. Basic echo server and client
  2. Custom message handling
  3. Event-driven communication
  4. Multiple client connections
  5. Sending structured data

Let’s import our modules:

from python_tcp.core import *
from python_tcp.server import *
from python_tcp.client import *
import threading
import time
import json

1. Basic Echo Server and Client

Let’s start with a simple echo server that repeats back whatever message it receives:

def run_echo_server():
    # Create and start a basic TCP server
    server = TCPServer(port=8000)
    server.start()
    
    print(f"Echo server running at {server.host}:{server.port}")
    return server

def run_echo_client(server_port=8000, messages=None):
    if messages is None:
        messages = ["Hello, server!", "How are you?", "Goodbye!"]
    
    # Create a TCP client
    client = TCPClient()
    
    # Connect to the server
    if client.connect(LOCALHOST, server_port):
        try:
            # Send each message and display the response
            for msg in messages:
                print(f"\nSending: {msg}")
                client.send(msg.encode('utf-8'))
                
                # Receive the response
                response = client.receive()
                if response:
                    print(f"Received: {response.decode('utf-8')}")
                else:
                    print("No response received!")
                    break
                
                # Small delay between messages
                time.sleep(0.5)
        finally:
            # Close the connection
            client.close()
    
    return client

Here’s how to run this example:

def echo_demo():
    # Start the server
    server = run_echo_server()
    
    try:
        # Give the server a moment to start
        time.sleep(1)
        
        # Run the client
        run_echo_client(server_port=server.port)
        
    finally:
        # Stop the server
        server.stop()

# Uncomment to run this demo
# echo_demo()

2. Custom Message Handling

Let’s create a server that processes messages rather than just echoing them:

def run_custom_handler_server():
    # Create an enhanced server
    server = EnhancedTCPServer(port=8000)
    
    # Define a custom message handler
    def message_handler(conn_id, data):
        text = data.decode('utf-8')
        print(f"Server processing: {text}")
        
        # Simple processing: count words and characters
        words = len(text.split())
        chars = len(text)
        
        # Return a formatted response
        response = f"Your message had {words} words and {chars} characters."
        return response.encode('utf-8')
    
    # Set the message handler
    server.set_message_handler(message_handler)
    
    # Start the server
    server.start()
    
    print(f"Custom handler server running at {server.host}:{server.port}")
    return server

And a client to test it:

def custom_handler_demo():
    # Start the server
    server = run_custom_handler_server()
    
    try:
        # Give the server a moment to start
        time.sleep(1)
        
        # Run the client with some test messages
        messages = [
            "Hello there!",
            "This is a longer message with multiple words to count.",
            "The quick brown fox jumps over the lazy dog."
        ]
        
        run_echo_client(server_port=server.port, messages=messages)
        
    finally:
        # Stop the server
        server.stop()

# Uncomment to run this demo
# custom_handler_demo()

3. Event-Driven Communication

Now let’s demonstrate event-driven communication:

def run_event_driven_server():
    # Create an event-driven server
    server = EventDrivenTCPServer(port=8000)
    
    # Set up event handlers
    server.on_connect = lambda conn_id, addr: print(f"SERVER EVENT: Client connected from {addr[0]}:{addr[1]} (ID: {conn_id})")
    server.on_disconnect = lambda conn_id: print(f"SERVER EVENT: Client disconnected (ID: {conn_id})")
    server.on_data = lambda conn_id, data: print(f"SERVER EVENT: Received data from {conn_id}: {data.decode('utf-8')}")
    
    # Set a message handler that uppercases the message
    def uppercase_handler(conn_id, data):
        text = data.decode('utf-8')
        response = text.upper()
        return response.encode('utf-8')
    
    server.set_message_handler(uppercase_handler)
    
    # Start the server
    server.start()
    
    print(f"Event-driven server running at {server.host}:{server.port}")
    return server

And a matching event-driven client:

def run_event_driven_client(server_port=8000, messages=None):
    if messages is None:
        messages = ["Hello, event-driven server!", "Processing events...", "Goodbye!"]
    
    # Create an event-driven client
    client = EventDrivenTCPClient()
    
    # Set up event handlers
    client.on_connect = lambda host, port: print(f"CLIENT EVENT: Connected to {host}:{port}")
    client.on_disconnect = lambda: print("CLIENT EVENT: Disconnected from server")
    client.on_data = lambda data: print(f"CLIENT EVENT: Received data: {data.decode('utf-8')}")
    client.on_error = lambda error: print(f"CLIENT EVENT: Error occurred: {error}")
    
    # Connect to the server
    if client.connect(LOCALHOST, server_port):
        try:
            # Send each message
            for msg in messages:
                print(f"\nSending: {msg}")
                client.send(msg.encode('utf-8'))
                
                # Wait for the response to be processed by the event handler
                time.sleep(1)
                
        finally:
            # Close the connection
            client.close()
    
    return client

Let’s see it in action:

def event_driven_demo():
    # Start the server
    server = run_event_driven_server()
    
    try:
        # Give the server a moment to start
        time.sleep(1)
        
        # Run the client
        run_event_driven_client(server_port=server.port)
        
    finally:
        # Stop the server
        server.stop()

# Uncomment to run this demo
# event_driven_demo()

4. Multiple Client Connections

Let’s demonstrate a server handling multiple clients simultaneously:

def multiple_clients_demo():
    # Start a basic echo server
    server = run_echo_server()
    
    try:
        # Give the server a moment to start
        time.sleep(1)
        
        # Create threads for multiple clients
        client_threads = []
        
        # Function to run in each client thread
        def client_task(client_id):
            messages = [
                f"Hello from client {client_id}",
                f"This is message 2 from client {client_id}",
                f"Goodbye from client {client_id}"
            ]
            
            print(f"Starting client {client_id}")
            run_echo_client(server_port=server.port, messages=messages)
            print(f"Client {client_id} finished")
        
        # Start multiple client threads
        for i in range(3):
            thread = threading.Thread(target=client_task, args=(i+1,))
            client_threads.append(thread)
            thread.start()
        
        # Wait for all clients to finish
        for thread in client_threads:
            thread.join()
            
    finally:
        # Stop the server
        server.stop()

# Uncomment to run this demo
# multiple_clients_demo()

5. Sending Structured Data (JSON)

Finally, let’s demonstrate sending structured data as JSON:

def run_json_server():
    # Create an enhanced server
    server = EnhancedTCPServer(port=8000)
    
    # Define a custom message handler for JSON data
    def json_handler(conn_id, data):
        try:
            # Parse the JSON data
            json_data = json.loads(data.decode('utf-8'))
            print(f"Server received JSON: {json_data}")
            
            # Process the data based on its type
            if json_data.get('type') == 'greeting':
                response = {
                    'type': 'greeting_response',
                    'message': f"Hello, {json_data.get('name', 'Anonymous')}!",
                    'timestamp': time.time()
                }
            elif json_data.get('type') == 'calculation':
                # Simple calculation
                a = json_data.get('a', 0)
                b = json_data.get('b', 0)
                op = json_data.get('operation', '+')
                
                result = None
                if op == '+':
                    result = a + b
                elif op == '-':
                    result = a - b
                elif op == '*':
                    result = a * b
                elif op == '/' and b != 0:
                    result = a / b
                
                response = {
                    'type': 'calculation_response',
                    'result': result,
                    'timestamp': time.time()
                }
            else:
                response = {
                    'type': 'error',
                    'message': 'Unknown request type',
                    'timestamp': time.time()
                }
            
            # Send the JSON response
            return json.dumps(response).encode('utf-8')
            
        except json.JSONDecodeError:
            # Handle invalid JSON
            error_response = {
                'type': 'error',
                'message': 'Invalid JSON format',
                'timestamp': time.time()
            }
            return json.dumps(error_response).encode('utf-8')
    
    # Set the message handler
    server.set_message_handler(json_handler)
    
    # Start the server
    server.start()
    
    print(f"JSON server running at {server.host}:{server.port}")
    return server

def run_json_client(server_port=8000):
    # Create a TCP client
    client = TCPClient()
    
    # Connect to the server
    if client.connect(LOCALHOST, server_port):
        try:
            # Send a greeting
            greeting = {
                'type': 'greeting',
                'name': 'Alice',
                'language': 'en'
            }
            
            print(f"\nSending greeting: {greeting}")
            client.send(json.dumps(greeting).encode('utf-8'))
            
            # Receive and parse the response
            response = client.receive()
            if response:
                json_response = json.loads(response.decode('utf-8'))
                print(f"Received response: {json_response}")
            
            # Send a calculation request
            calculation = {
                'type': 'calculation',
                'a': 10,
                'b': 5,
                'operation': '*'
            }
            
            print(f"\nSending calculation: {calculation}")
            client.send(json.dumps(calculation).encode('utf-8'))
            
            # Receive and parse the response
            response = client.receive()
            if response:
                json_response = json.loads(response.decode('utf-8'))
                print(f"Received response: {json_response}")
                
            # Send an invalid request type
            invalid = {
                'type': 'unknown_type',
                'data': 'test'
            }
            
            print(f"\nSending invalid type: {invalid}")
            client.send(json.dumps(invalid).encode('utf-8'))
            
            # Receive and parse the response
            response = client.receive()
            if response:
                json_response = json.loads(response.decode('utf-8'))
                print(f"Received response: {json_response}")
                
        finally:
            # Close the connection
            client.close()
    
    return client

Let’s run the JSON demo:

def json_demo():
    # Start the server
    server = run_json_server()
    
    try:
        # Give the server a moment to start
        time.sleep(1)
        
        # Run the client
        run_json_client(server_port=server.port)
        
    finally:
        # Stop the server
        server.stop()

# Uncomment to run this demo
# json_demo()

Conclusion

Through these examples, we’ve demonstrated how our TCP server and client implementations can be used in various scenarios:

  1. A simple echo server and client
  2. Custom message handling
  3. Event-driven communication
  4. Multiple simultaneous client connections
  5. Structured data exchange using JSON

These examples show the flexibility of our implementation and how it can be adapted to different use cases.