突破单线程服务器限制:多线程、异步服务器架构解析
立即解锁
发布时间: 2025-09-01 02:03:08 阅读量: 6 订阅数: 27 AIGC 

# 突破单线程服务器限制:多线程、异步服务器架构解析
## 1. 引言
在网络编程中,单线程服务器存在一定的局限性,无法高效地同时处理多个客户端的请求。为了克服这些限制,我们可以采用多线程或异步服务器架构。接下来,我们将详细探讨这两种策略。
## 2. 多进程和多线程服务器
### 2.1 原理与优势
若要让服务器同时与多个客户端通信,一种常见的解决方案是利用操作系统对多线程或多进程的支持。可以创建具有相同内存占用的线程,或者创建相互独立运行的进程,使多个控制线程能够独立通过同一段代码。这种技术的优势在于简单性,只需启动单线程服务器代码的多个副本即可。
### 2.2 劣势
然而,这种方法也存在明显的劣势。可通信的客户端数量受操作系统并发功能的扩展限制。即使是空闲或慢速的客户端,也会占用整个线程或进程的注意力,消耗系统内存和进程表中的插槽。当同时运行数千个或更多线程时,操作系统的扩展性通常较差,并且系统从一个客户端转移注意力到另一个客户端时所需的上下文切换会使服务随着受欢迎程度的增加而变慢。
### 2.3 多线程服务器示例
以下是一个多线程服务器的示例代码:
```python
#!/usr/bin/env python3
# Network Programming in Python: The Basics
# Using multiple threads to serve several clients in parallel.
import zen_utils
from threading import Thread
def start_threads(listener, workers=4):
t = (listener,)
for i in range(workers):
Thread(target=zen_utils.accept_connections_forever,
args=t).start()
if __name__ == '__main__':
address = zen_utils.parse_command_line('multi-threaded server')
listener = zen_utils.create_srv_socket(address)
start_threads(listener)
```
这个示例展示了一种可能的多线程程序设计:主线程生成 n 个服务器线程,然后退出,确保这些线程能使进程无限期运行。当然,还有其他可能性,例如主线程可以继续作为服务器线程,或者作为监视器,偶尔检查 n 个服务器线程是否仍在运行,并在有线程死亡时重启替换线程。
### 2.4 多线程模式的特点
所有这些模式都有一个共同点:无论客户端当前是否正在发出请求,都会为每个连接的客户端分配一个相对昂贵的操作系统可见的控制线程。不过,由于服务器代码在由多个线程控制时可以保持不变(假设每个线程建立自己的数据库连接并打开文件,消除了线程之间的资源协调需求),因此很容易在服务器的工作负载上测试多线程方法。如果它能够处理请求负载,其简单性使其成为内部服务(不向公众开放)的特别有吸引力的策略,因为攻击者无法轻易打开空闲连接,直到线程或进程池耗尽。
### 2.5 旧的 SocketServer 框架
利用操作系统可见的控制线程同时处理多个客户端交互的模式非常常见,Python 标准库中集成了体现这种模式的框架。`socketserver` 模块(在 Python 2 中称为 `SocketServer`)将服务器模式(了解如何打开监听套接字并接受新的客户端连接)与处理程序模式(了解如何通过打开的套接字进行通信)分开。以下是一个基于标准库服务器模式的线程服务器示例:
```python
#!/usr/bin/env python3
# Network Programming in Python: The Basics
# Uses the legacy “socketserver” Standard Library module to write a server.
from socketserver import BaseRequestHandler, TCPServer, ThreadingMixIn
import zen_utils
class ZenHandler(BaseRequestHandler):
def handle(self):
zen_utils.handle_conversation(self.request, self.client_address)
class ZenServer(ThreadingMixIn, TCPServer):
allow_reuse_address = 1
# address_family = socket.AF_INET6 # uncomment if you need IPv6
if __name__ == '__main__':
address = zen_utils.parse_command_line('legacy “SocketServer” server')
server = ZenServer(address, ZenHandler)
server.serve_forever()
```
与前面启动固定数量线程的示例不同,这个示例允许连接的客户端池决定启动多少线程,服务器上运行的线程数量没有限制。这使得攻击者可以轻松使服务器瘫痪,因此不建议将这个标准库模块用于生产和面向客户的服务。
## 3. 异步服务器
### 3.1 原理
如何在向客户端发送响应和接收其下一个请求之间保持 CPU 忙碌,而不必为每个客户端支付操作系统可见的控制线程的成本呢?答案是创建异步模式的服务器。异步模式意味着代码不会阻塞并等待单个客户端的数据到来或离开,而是愿意监听一长串客户端套接字,并在其中任何一个准备好进行更多交互时做出响应。
### 3.2 现代操作系统网络栈的支持
现代操作系统网络栈的两个方面支持这种模式:
- 提供系统函数,允许进程阻塞等待完整的客户端套接字列表,而不仅仅是一个,从而使单个线程能够同时服务数百或数千个客户端套接字。
- 套接字可以配置为非阻塞,这意味着它在 `send()` 或 `recv()` 调用中永远不会阻塞调用线程,并且无论在对话中是否可以取得更多进展,都会立即从 `send()` 或 `recv()` 系统调用返回。如果进展缓慢,调用者必须在客户端似乎准备好进行更多交互时再次尝试。
### 3.3 异步服务器示例
以下是一个原始异步事件循环的示例代码:
```python
#!/usr/bin/env python3
# Network Programming in Python: The Basics
# Asynchronous I/O driven directly by the poll() system call.
import select, zen_utils
def all_events_forever(poll_object):
while True:
for fd, event in poll_object.poll():
yield fd, event
def serve(listener):
sockets = {listener.fileno(): listener}
addresses = {}
bytes_received = {}
bytes_to_send = {}
poll_object = select.poll()
poll_object.register(listener, select.POLLIN)
for fd, event in all_events_forever(poll_object):
sock = sockets[fd]
# Socket closed: remove it from our data structures.
if event & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
address = addresses.pop(sock)
rb = bytes_received.pop(sock, b'')
sb = bytes_to_send.pop(sock, b'')
if rb:
print('Client {} sent {} but then closed'.format(address, rb))
elif sb:
print('Client {} closed before we sent {}'.format(address, sb))
else:
print('Client {} closed socket normally'.format(address))
poll_object.unregister(fd)
del sockets[fd]
# New socket: add it to our data structures.
elif sock is listener:
sock, address = sock.accept()
print('Accepted connection from {}'.format(address))
sock.setblocking(False) # force socket.timeout if we blunder
sockets[sock.fileno()] = sock
addresses[sock] = address
poll_object.register(sock, select.POLLIN)
# Incoming data: keep receiving until we see the suffix
```
0
0
复制全文
相关推荐









