Implementing Different Server Models in Python
Single-Process Server Model
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 8000))
server_socket.listen(128)
while True:
client_socket, client_address = server_socket.accept()
while True:
data = client_socket.recv(1024)
if data:
print(f'Received from {client_address}: {data.decode()}')
else:
print(f'Client {client_address} disconnected')
break
client_socket.close()
server_socket.close()
Key characteristics:
- Handles one client at a time
- Simple but inefficient for multiple clients
- Client disconnection detected by empty recv()
Multi-Process Server Model
import socket
from multiprocessing import Process
def client_handler(client_socket, client_address):
while True:
data = client_socket.recv(1024)
if data:
print(f'Received from {client_address}: {data.decode()}')
else:
print(f'Client {client_address} disconnected')
break
client_socket.close()
if __name__ == '__main__':
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 8000))
server_socket.listen(128)
while True:
client_socket, client_address = server_socket.accept()
Process(target=client_handler, args=(client_socket, client_address)).start()
client_socket.close()
Key characteristics:
- Creates new process for each client
- Better for moderate client counts
- Resource-intensive for many clients
Non-Blocking Single-Process Server
import socket
active_clients = []
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 8000))
server_socket.listen(128)
server_socket.setblocking(False)
while True:
try:
client_socket, client_address = server_socket.accept()
client_socket.setblocking(False)
active_clients.append((client_socket, client_address))
except BlockingIOError:
pass
disconnected_clients = []
for client in active_clients:
sock, addr = client
try:
data = sock.recv(1024)
if data:
print(f'Received from {addr}: {data.decode()}')
else:
sock.close()
disconnected_clients.append(client)
except BlockingIOError:
pass
for client in disconnected_clients:
active_clients.remove(client)
Key characteristics:
- Single process handles multiple clients
- Uses non-blocking sockets
- Requires polling mechanism
Select-Based Server
import select
import socket
import queue
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 8080))
server_socket.listen(128)
inputs = [server_socket]
outputs = []
message_queues = {}
while inputs:
readable, writable, exceptional = select.select(inputs, outputs, inputs)
for sock in readable:
if sock is server_socket:
client_socket, client_address = sock.accept()
client_socket.setblocking(False)
inputs.append(client_socket)
message_queues[client_socket] = queue.Queue()
else:
data = sock.recv(1024)
if data:
message_queues[sock].put(data)
if sock not in outputs:
outputs.append(sock)
else:
if sock in outputs:
outputs.remove(sock)
inputs.remove(sock)
sock.close()
del message_queues[sock]
for sock in writable:
try:
next_msg = message_queues[sock].get_nowait()
except queue.Empty:
outputs.remove(sock)
else:
sock.send(next_msg)
Key characteristics:
- Uses select() system call
- Handles multiple clients efficient
- Limited by FD_SETSIZE (typically 1024)
Epoll-Based Server
import select
import socket
import queue
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 8080))
server_socket.listen(128)
epoll = select.epoll()
epoll.register(server_socket.fileno(), select.EPOLLIN)
connections = {}
addresses = {}
queues = {}
while True:
events = epoll.poll(1)
for fd, event in events:
if fd == server_socket.fileno():
client_socket, client_address = server_socket.accept()
client_socket.setblocking(False)
fd = client_socket.fileno()
epoll.register(fd, select.EPOLLIN)
connections[fd] = client_socket
addresses[fd] = client_address
queues[fd] = queue.Queue()
elif event & select.EPOLLIN:
data = connections[fd].recv(1024)
if data:
queues[fd].put(data)
epoll.modify(fd, select.EPOLLOUT)
else:
epoll.unregister(fd)
connections[fd].close()
del connections[fd]
del addresses[fd]
del queues[fd]
elif event & select.EPOLLOUT:
try:
msg = queues[fd].get_nowait()
except queue.Empty:
epoll.modify(fd, select.EPOLLIN)
else:
connections[fd].send(msg)
Key characteristics:
- More efficient than select()
- No FD_SETSIZE limitation
- Edge-triggered or level-triggered modes
Coroutine-Based Server
import gevent
from gevent import socket, monkey
monkey.patch_all()
def handle_client(client_socket):
while True:
data = client_socket.recv(1024)
if not data:
client_socket.close()
break
print(f"Received: {data}")
client_socket.send(data)
def server(port):
server_socket = socket.socket()
server_socket.bind(('', port))
server_socket.listen(5)
while True:
client_socket, addr = server_socket.accept()
gevent.spawn(handle_client, client_socket)
if __name__ == '__main__':
server(7788)
Key characterisitcs:
- Lightweight green threads
- Automatic context switching
- Ideal for I/O-bound applications