from python_tcp.core import *
from python_tcp.server import *
from python_tcp.client import *
import threading
import time
import json
TCP 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
= TCPServer(port=8000)
server
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:
= ["Hello, server!", "How are you?", "Goodbye!"]
messages
# Create a TCP client
= TCPClient()
client
# 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}")
'utf-8'))
client.send(msg.encode(
# Receive the response
= client.receive()
response if response:
print(f"Received: {response.decode('utf-8')}")
else:
print("No response received!")
break
# Small delay between messages
0.5)
time.sleep(finally:
# Close the connection
client.close()
return client
Here’s how to run this example:
def echo_demo():
# Start the server
= run_echo_server()
server
try:
# Give the server a moment to start
1)
time.sleep(
# Run the client
=server.port)
run_echo_client(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
= EnhancedTCPServer(port=8000)
server
# Define a custom message handler
def message_handler(conn_id, data):
= data.decode('utf-8')
text print(f"Server processing: {text}")
# Simple processing: count words and characters
= len(text.split())
words = len(text)
chars
# Return a formatted response
= f"Your message had {words} words and {chars} characters."
response 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
= run_custom_handler_server()
server
try:
# Give the server a moment to start
1)
time.sleep(
# 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."
]
=server.port, messages=messages)
run_echo_client(server_port
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
= EventDrivenTCPServer(port=8000)
server
# Set up event handlers
= lambda conn_id, addr: print(f"SERVER EVENT: Client connected from {addr[0]}:{addr[1]} (ID: {conn_id})")
server.on_connect = lambda conn_id: print(f"SERVER EVENT: Client disconnected (ID: {conn_id})")
server.on_disconnect = lambda conn_id, data: print(f"SERVER EVENT: Received data from {conn_id}: {data.decode('utf-8')}")
server.on_data
# Set a message handler that uppercases the message
def uppercase_handler(conn_id, data):
= data.decode('utf-8')
text = text.upper()
response 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:
= ["Hello, event-driven server!", "Processing events...", "Goodbye!"]
messages
# Create an event-driven client
= EventDrivenTCPClient()
client
# Set up event handlers
= lambda host, port: print(f"CLIENT EVENT: Connected to {host}:{port}")
client.on_connect = lambda: print("CLIENT EVENT: Disconnected from server")
client.on_disconnect = lambda data: print(f"CLIENT EVENT: Received data: {data.decode('utf-8')}")
client.on_data = lambda error: print(f"CLIENT EVENT: Error occurred: {error}")
client.on_error
# Connect to the server
if client.connect(LOCALHOST, server_port):
try:
# Send each message
for msg in messages:
print(f"\nSending: {msg}")
'utf-8'))
client.send(msg.encode(
# Wait for the response to be processed by the event handler
1)
time.sleep(
finally:
# Close the connection
client.close()
return client
Let’s see it in action:
def event_driven_demo():
# Start the server
= run_event_driven_server()
server
try:
# Give the server a moment to start
1)
time.sleep(
# Run the client
=server.port)
run_event_driven_client(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
= run_echo_server()
server
try:
# Give the server a moment to start
1)
time.sleep(
# 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}")
=server.port, messages=messages)
run_echo_client(server_portprint(f"Client {client_id} finished")
# Start multiple client threads
for i in range(3):
= threading.Thread(target=client_task, args=(i+1,))
thread
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
= EnhancedTCPServer(port=8000)
server
# Define a custom message handler for JSON data
def json_handler(conn_id, data):
try:
# Parse the JSON data
= json.loads(data.decode('utf-8'))
json_data 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
= json_data.get('a', 0)
a = json_data.get('b', 0)
b = json_data.get('operation', '+')
op
= None
result if op == '+':
= a + b
result elif op == '-':
= a - b
result elif op == '*':
= a * b
result elif op == '/' and b != 0:
= a / b
result
= {
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
= TCPClient()
client
# 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}")
'utf-8'))
client.send(json.dumps(greeting).encode(
# Receive and parse the response
= client.receive()
response if response:
= json.loads(response.decode('utf-8'))
json_response print(f"Received response: {json_response}")
# Send a calculation request
= {
calculation 'type': 'calculation',
'a': 10,
'b': 5,
'operation': '*'
}
print(f"\nSending calculation: {calculation}")
'utf-8'))
client.send(json.dumps(calculation).encode(
# Receive and parse the response
= client.receive()
response if response:
= json.loads(response.decode('utf-8'))
json_response print(f"Received response: {json_response}")
# Send an invalid request type
= {
invalid 'type': 'unknown_type',
'data': 'test'
}
print(f"\nSending invalid type: {invalid}")
'utf-8'))
client.send(json.dumps(invalid).encode(
# Receive and parse the response
= client.receive()
response if response:
= json.loads(response.decode('utf-8'))
json_response 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
= run_json_server()
server
try:
# Give the server a moment to start
1)
time.sleep(
# Run the client
=server.port)
run_json_client(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.