一. 连接池的原理
首先, HTTP连接是基于TCP连接的, 与服务器之间进行HTTP通信, 本质就是与服务器之间建立了TCP连接后, 相互收发基于HTTP协议的数据包. 因此, 如果我们需要频繁地去请求某个服务器的资源, 我们就可以一直维持与个服务器的TCP连接不断开, 然后在需要请求资源的时候, 把连接拿出来用就行了.
一个项目可能需要与服务器之间同时保持多个连接, 比如一个爬虫项目, 有的线程需要请求服务器的网页资源, 有的线程需要请求服务器的图片等资源, 而这些请求都可以建立在同一条TCP连接上.
因此, 我们使用一个管理器来对这些连接进行管理, 任何程序需要使用这些连接时, 向管理器申请就可以了, 等到用完之后再将连接返回给管理器, 以供其他程序重复使用, 这个管理器就是连接池.
二. 实现代码
1. HTTPConnectionPool类
基于上一章的分析, 连接池应该是一个收纳连接的容器, 同时对这些连接有管理能力:
class HTTPConnectionPool:
def __init__(self, host: str, port: int = None, max_size: int = None, idle_timeout: int = None) -> None:
"""
:param host: pass
:param port: pass
:param max_size: 同时存在的最大连接数, 默认None->连接数无限,没了就创建
:param idle_timeout: 单个连接单次最长空闲时间,超时自动关闭,默认None->不限时
"""
self.host = host
self.port = port
self.max_size = max_size
= idle_timeout
self._lock = ()
= []
# 这里的conn_num指的是总连接数,包括其它线程拿出去正在使用的连接
self.conn_num = 0
self.is_closed = False
def acquire(self, blocking: bool = True, timeout: int = None) -> WrapperHTTPConnection:
...
def release(self, conn: WrapperHTTPConnection) -> None:
...
因此, 我们定义这样一个HTTPConnectionPool类, 使用一个列表来保存可用的连接. 对于外部来说, 只需要调用这个连接池对象的acquire和release方法就能取得和释放连接.
2. 线程安全地管理连接
对于线程池内部来说, 至少需要三个关于连接的操作: 从连接池中取得连接, 将连接放回连接池, 以及创建一个连接:
def _get_connection(self) -> WrapperHTTPConnection:
# 这个方法会把连接从_idle_conn移动到_used_conn列表中,并返回这个连接
try:
return .pop()
except IndexError:
raise EmptyPoolError
def _put_connection(self, conn: WrapperHTTPConnection) -> None:
(conn)
def _create_connection(self) -> WrapperHTTPConnection:
self.conn_num += 1
return WrapperHTTPConnection(self, HTTPConnection(self.host, self.port))
对于连接池外部来说, 主要有申请连接和释放连接这两个操作, 实际上这就是个简单的生产者消费者模型. 考虑到外部可能是多线程的环境, 我们使用来保证线程安全. 关于Condition的资料可以看这里.
def acquire(self, blocking: bool = True, timeout: int = None) -> WrapperHTTPConnection:
if self.is_closed:
raise ConnectionPoolClosed
with self._lock:
if self.max_size is None or not self.is_full():
# 在还能创建新连接的情况下,如果没有空闲连接,直接创建一个就行了
if self.is_pool_