HAProxy 研究笔记 -- TCP 连接处理流程

15090阅读 7评论2013-06-22 Godbach
分类:LINUX

本文基于 HAProxy 1.5-dev7 版本。

1. 关键数据结构 session
haproxy 负责处理请求的核心数据结构是 struct session,本文不对该数据结构进行分析。

从业务的处理的角度,简单介绍一下对 session 的理解:

此外,一个 session,通常还要对应一个 task,haproxy 最终用来做调度的是通过 task。

2. 相关初始化

在 haproxy 正式处理请求之前,会有一系列初始化动作。这里介绍和请求处理相关的一些初始化。

2.1. 初始化处理 TCP 连接的方法

初始化处理 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,
};

2.2. 初始化 listener

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 新建连接处理过程,基本上就是这三个函数的分析。

2.3. 绑定所有已注册协议上的 listeners

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

2.4. 启用所有已注册协议上的 listeners

把所有 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

3. TCP 连接的处理流程

3.1. 接受新建连接

前面几个方面的分析,主要是为了搞清楚当请求到来时,处理过程中实际的函数调用关系。以下分析 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 主要完成以下功能

3.2. TCP 连接上的接收事件

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 主要完成以下功能

3.3. TCP 连接上的发送事件

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主要完成以下功能

3.4. http 请求的处理

haproxy.c 
-> run_poll_loop 
-> process_runnable_tasks,查找当前待处理的任务所有 tasks, 然后调用 task->process(大多时候就是 process_session) 进行处理
-> process_session

process_session 主要完成以下功能

本文简单分析了 TCP 连接的处理过程,不侧重细节分析,而且缺少后端 server 的选择以及建连等,重在希望展示出一个 haproxy 处理 TCP 连接的框架。


上一篇:HAProxy 研究笔记
下一篇:HAProxy 研究笔记 -- rules 实现

文章评论