from python_tcp.core import *
from python_tcp.server import *
from python_tcp.client import *
import threading
import time
import jsonTCP Server and Client Examples
Introduction
In this notebook, we’ll see practical examples of using our TCP server and client implementations. We’ll demonstrate:
- Basic echo server and client
- Custom message handling
- Event-driven communication
- Multiple client connections
- Sending structured data
Let’s import our modules:
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 clientHere’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 serverAnd 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 serverAnd 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 clientLet’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 clientLet’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:
- A simple echo server and client
- Custom message handling
- Event-driven communication
- Multiple simultaneous client connections
- Structured data exchange using JSON
These examples show the flexibility of our implementation and how it can be adapted to different use cases.