分类: LINUX
2014-06-26 19:26:52
原文地址:HAProxy 研究笔记 -- TCP 连接处理流程 作者:Godbach
1. 关键数据结构 session
haproxy 负责处理请求的核心数据结构是 struct session,本文不对该数据结构进行分析。
从业务的处理的角度,简单介绍一下对 session 的理解:
此外,一个 session,通常还要对应一个 task,haproxy 最终用来做调度的是通过 task。
在 haproxy 正式处理请求之前,会有一系列初始化动作。这里介绍和请求处理相关的一些初始化。
初始化处理 TCP 协议的相关数据结构,主要是和 socket 相关的方法的声明。详细见下面 proto_tcpv4 (proto_tcp.c)的初始化:
static struct protocol proto_tcpv4 = { .name = "tcpv4", .sock_domain = AF_INET, .sock_type = SOCK_STREAM, .sock_prot = IPPROTO_TCP, .sock_family = AF_INET, .sock_addrlen = sizeof(struct sockaddr_in), .l3_addrlen = 32/8, .accept = &stream_sock_accept, .read = &stream_sock_read, .write = &stream_sock_write, .bind = tcp_bind_listener, .bind_all = tcp_bind_listeners, .unbind_all = unbind_all_listeners, .enable_all = enable_all_listeners, .listeners = LIST_HEAD_INIT(proto_tcpv4.listeners), .nb_listeners = 0, };
listener,顾名思义,就是用于负责处理监听相关的逻辑。
在 haproxy 解析 bind 配置的时候赋值给 listener 的 proto 成员。函数调用流程如下:
cfgparse.c -> cfg_parse_listen -> str2listener -> tcpv4_add_listener -> listener->proto = &proto_tcpv4;
由于这里初始化的是 listener 处理 socket 的一些方法。可以推断, haproxy 接收 client 新建连接的入口函数应该是 protocol 结构体中的 accpet 方法。对于tcpv4 来说,就是 stream_sock_accept() 函数。该函数到 1.5-dev19 中改名为 listener_accept()。这是后话,暂且不表。
listener 的其他初始化
cfgparse.c -> check_config_validity -> listener->accept = session_accept; listener->frontend = curproxy; (解析 frontend 时,会执行赋值: curproxy->accept = frontend_accept) listener->handler = process_session;
整个 haproxy 配置文件解析完毕,listener 也已初始化完毕。可以简单梳理一下几个 accept 方法的设计逻辑:
下文分析 TCP 新建连接处理过程,基本上就是这三个函数的分析。
haproxy.c -> protocol_bind_all -> all registered protocol bind_all -> tcp_bind_listeners (TCP) -> tcp_bind_listener -> [ fdtab[fd].cb[DIR_RD].f = listener->proto->accept ]
该函数指针指向 proto_tcpv4 结构体的 accept 成员,即函数 stream_sock_accept
把所有 listeners 的 fd 加到 polling lists 中 haproxy.c -> protocol_enable_all -> all registered protocol enable_all -> enable_all_listeners (TCP) -> enable_listener 函数会将处于 LI_LISTEN 的 listener 的状态修改为 LI_READY,并调用 cur poller 的 set 方法, 比如使用 sepoll,就会调用 __fd_set
前面几个方面的分析,主要是为了搞清楚当请求到来时,处理过程中实际的函数调用关系。以下分析 TCP 建连过程。
haproxy.c -> run_poll_loop -> cur_poller.poll -> __do_poll (如果配置使用的是 sepoll,则调用 ev_sepoll.c 中的 poll 方法) -> fdtab[fd].cb[DIR_RD].f(fd) (TCP 协议的该函数指针指向 stream_sock_accept ) -> stream_sock_accept -> 按照 global.tune.maxaccept 的设置尽量可能多执行系统调用 accept,然后再调用 l->accept(),即 listener 的 accept 方法 session_accept -> session_accept
session_accept 主要完成以下功能
haproxy.c -> run_poll_loop -> cur_poller.poll -> __do_poll (如果配置使用的是 sepoll,则调用 ev_sepoll.c 中的 poll 方法) -> fdtab[fd].cb[DIR_RD].f(fd) (该函数在建连阶段被初始化为四层协议的 read 方法,对于 TCP 协议,为 stream_sock_read ) -> stream_sock_read
stream_sock_read 主要完成以下功能
struct buffer *b = si->ib
haproxy.c -> run_poll_loop -> cur_poller.poll -> __do_poll (如果配置使用的是 sepoll,则调用 ev_sepoll.c 中的 poll 方法) -> fdtab[fd].cb[DIR_WR].f(fd) (该函数在建连阶段被初始化为四层协议的 write 方法,对于 TCP 协议,为 stream_sock_write ) -> stream_sock_write
stream_sock_write主要完成以下功能
struct buffer *b = si->ob
haproxy.c -> run_poll_loop -> process_runnable_tasks,查找当前待处理的任务所有 tasks, 然后调用 task->process(大多时候就是 process_session) 进行处理 -> process_session
process_session 主要完成以下功能