🔗 典型请求流程
注意:此信息仍在进行中。编号条目已针对 Squid-3 进行更新。
- 客户端连接由 Comm::TcpAcceptor 接受,传递给 client-side socket support 并进行解析,或者通过 clientBeginRequest 直接创建内部 Squid 请求。
- 如果流量被拦截,则执行 Host: 标头验证。
- 检查 http_access 控制。客户端请求构建 ACL 状态数据结构,并注册一个回调函数,以便在访问控制检查完成后收到通知。- 可能会执行身份验证 - 可能会执行 deny_info 重定向
- 执行 ICAP REQMOD 适配。- 可能会生成具有任何 HTTP 状态的 ICAP 响应。
- 执行 URL 重写适配。- 可能会使用 3xx HTTP 状态码执行 HTTP 重定向。
以下信息已过时,似乎适用于 Squid-2
- 客户端请求被转发到客户端流中的 GetMoreData,该函数在缓存中查找请求的对象,以及/或同对象的 Vary: 版本。如果是缓存命中,则客户端注册其对 StoreEntry 的兴趣。否则,Squid 需要转发请求,可能带有 If-Modified-Since 标头。
- 请求转发过程始于
protoDispatch。此函数开始对等选择过程,该过程可能涉及发送 ICP 查询和接收 ICP 回复。对等选择过程还包括检查 never_direct 和 always_direct 等配置选项。 - 处理完(如果有)ICP 回复后,我们会到达 protoStart。此函数调用适当的特定协议函数来转发请求。这里我们假设它是一个 HTTP 请求。
- HTTP 模块首先打开与源服务器或缓存对等方的连接。如果没有可用的空闲持久套接字,则将新的连接请求连同回调函数一起交给网络通信模块。comm.c 例程在放弃之前可能会尝试多次建立连接。
- 建立 TCP 连接后,HTTP 会构建一个请求缓冲区并提交给套接字进行写入。然后,它会注册一个读取处理程序以接收和处理 HTTP 回复。
- 当回复最初接收到时,HTTP 回复标头会被解析并放入回复数据结构。随着回复数据的读取,它会被追加到 StoreEntry。每次将数据追加到 StoreEntry 时,都会通过回调函数通知客户端有关新数据的信息。读取的速率由延迟池例程通过延迟读取机制进行调节。
- 当客户端收到新数据的通知时,它会将数据从 StoreEntry 复制并提交给客户端套接字进行写入。
- 当数据追加到 StoreEntry 并被客户端读取时,数据可能会被提交给磁盘进行写入。
- 当 HTTP 模块完成从上游服务器读取回复时,它会将 StoreEntry 标记为 complete。服务器套接字会被关闭,或者提供给持久连接池以供将来使用。
- 当客户端已写入所有对象数据后,它会从 StoreEntry 中取消注册自己。同时,它会等待客户端的另一个请求,或者关闭客户端连接。
🔗 主循环:comm_select()
Squid 的核心是 select(2) 系统调用。Squid 使用 select() 或 poll(2) 或 kqueue 或 epoll 或 /dev/poll 来处理所有打开文件描述符上的 I/O。此后,我们将仅使用 select 来泛指任一系统调用。
select() 和 poll() 系统调用通过等待一组文件描述符上的 I/O 事件来工作。Squid 只检查 读取 和 写入 事件。当给定文件描述符注册了读取或写入处理程序时,Squid 就知道应该检查读取或写入。处理函数通过 commSetSelect 函数进行注册。例如:
commSetSelect(fd, COMM_SELECT_READ, clientReadRequest, conn, 0);
在此示例中,fd 是到客户端连接的 TCP 套接字。当套接字上有数据可供读取时,select 循环将执行
clientReadRequest(fd, conn);
I/O 处理程序在每次调用时都会被重置。换句话说,如果处理函数希望继续读取或写入文件描述符,它必须通过 commSetSelect 重新注册自己。I/O 处理程序可以在被调用之前被取消,方法是提供 NULL 参数,例如:
commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
这些 I/O 处理程序(及其他)及其关联的回调数据指针保存在 fde 数据结构中
struct _fde {
...
PF *read_handler;
void *read_data;
PF *write_handler;
void *write_data;
close_handler *close_handler;
DEFER *defer_check;
void *defer_data;
};
read_handler 和 write_handler 分别在文件描述符准备好读取或写入时调用。close_handler 在文件描述符关闭时调用。close_handler 实际上是一个回调函数链表。
在某些情况下,我们希望延迟从文件描述符读取数据,即使它有数据可供我们读取。当来自服务器端的数据比写入客户端的速度快时,可能就会出现这种情况。在将文件描述符添加到 select 的 read set 之前,我们会调用 defer_check(如果它不是 NULL)。如果 defer_check 返回 1,那么我们将在 select 循环的这次跳过该文件描述符。
这些处理程序存储在 FD_ENTRY 结构中,该结构定义在 comm.h 中。 fd_table[] 是 FD_ENTRY 结构的全局数组。处理函数是 PF 类型,这是一个 typedef
typedef void (*PF) (int, void *);
close 处理程序实际上是一个处理函数链表。每个处理程序还具有一个关联的指针 (void *data),指向某种数据结构。
comm_select() 是发出 select() 系统调用的函数。它扫描整个 fd_table[] 数组以查找处理函数。每个具有读取处理程序的文件描述符都将在 fd_set 读取位掩码中设置。类似地,扫描写入处理程序并为写入位掩码设置位。然后调用 select(),并扫描返回的读取和写入位掩码,以查找具有待处理 I/O 的描述符。对于每个就绪的描述符,都会调用处理程序。请注意,在调用处理程序之前,会将其从 FD_ENTRY 中清除。
每个处理程序被调用后,都会调用 comm_select_incoming() 来处理新的 HTTP 和 ICP 请求。
典型的读取处理程序是 httpReadReply(), diskHandleRead(), icpHandleUdp(), 和 ipcache_dnsHandleRead()。典型的写入处理程序是 commHandleWrite(), diskHandleWrite(), 和 icpUdpReply()。处理函数使用 commSetSelect() 设置,除了 close 处理程序,它们是使用 comm_add_close_handler() 设置的。
close 处理程序通常从 comm_close() 调用。close 处理程序的任务是释放与文件描述符关联的数据结构。因此,comm_close() 通常必须是序列中的最后一个函数,以防止访问刚刚释放的内存。
超时和生存期处理程序会在文件描述符空闲时间过长时被调用。它们将在后续章节中进一步讨论。
导航:网站搜索,网站页面,分类,🔼 向上