mongoose源码分析系列之active_connections

本文详细介绍了网络服务器管理中连接管理的实现方式,包括结构体定义、初始化、连接添加与删除等核心过程。此外,还深入解析了连接状态标记及其在不同场景下的应用。
目录(?) [-]
  1. 所在的结构体定义
  2. 变量初始化
  3. 向链表中添加元素
  4. 轮询所有的active_connections
  5. 从链表中移除节点
  6. close_conn函数调用处分析
    1. conn-flags中CONN_CLOSE标记
    2. conn-flags中CONN_SPOOL_DONE标记

1. 所在的结构体定义

struct mg_server {
sock_t listening_sock;
union socket_address lsa; // Listening socket address
struct ll active_connections;
struct ll uri_handlers;
char *config_options[NUM_OPTIONS];
void *server_data;
void *ssl_ctx; // SSL context
sock_t ctl[2]; // Control socketpair. Used to wake up from select() call
};
active_connections是一个双向循环链表:
struct ll { struct ll *prev, *next; };

2. 变量初始化

mg_create_server函数:
LINKED_LIST_INIT(&server->active_connections);
#define LINKED_LIST_INIT(N) ((N)->next = (N)->prev = (N))

3. 向链表中添加元素

accept_new_connection函数
该函数又是一个高级的if else if else的写法,作者的意图操作是:
  • if(执行一个语句并判断)
  • else if(再执行一个语句并判断) {异常处理}
  • else if(再执行一个语句并判断) {异常处理}
  • else {最终执行一些操作}
这里的厉害之处就在于:
每次if或者else if执行完语句后,故意让预期表达式为false,同而可以执行下一个分支,这样正常情况下,上面的三个if或else if判断中的表达式都会执行到,同时保证最终进入到else中完成所有的处理。

下面来分析函数的具体作用:
  • sock = accept(server->listening_sock, &sa.sa, &len) accept一个连接
  • check_acl(server->config_options[ACCESS_CONTROL_LIST], ntohl(* (uint32_t *) &sa.sin.sin_addr) 检查连接不是acl ACCESS_CONTROL_LIST
  • conn = (struct connection *) calloc(1, sizeof(*conn)) calloc一个struct connection大小的空间,将指针赋给conn
  • set_close_on_exec(sock); and set_non_blocking_mode(sock); 给sock设置参数
  • 然后赋值:conn->server、conn->client_sock、conn->mg_conn.remote_ip、conn->mg_conn.remote_port以及conn->mg_conn.server_param
  • LINKED_LIST_ADD_TO_FRONT(&server->active_connections, &conn->link); 将conn->link添加到server->active_connections循环链表中
注意:这里只是将conn->link添加到server->active_connections循环链表中,并没有添加和conn的指针相关的元素,这里又是一个技巧。获取conn的地方使用的另外一个宏是LINKED_LIST_ENTRY:
#define LINKED_LIST_ENTRY(P,T,N) ((T *)((char *)(P) - offsetof(T, N)))
调用时,传递的三个参数是:struct ll *变量、struct connection结构体以及link成员变量,一个使用的例子如下:
conn = LINKED_LIST_ENTRY(lp, struct connection, link);
LINKED_LIST_ENTRY宏定义分解来看:
  • (char *)(P) 将P指针强转为char *
  • offsetof(T, N) 计算结构体T中成员N在该结构体中的偏移量
  • ((char *)(P) - offsetof(T, N)) 将P的指针往前减少相应的偏移量,即获得结构体T的指针
  • ((T *)((char *)(P) - offsetof(T, N))) 最后强转为(T *)类型

4. 轮询所有的active_connections

mg_poll_server函数中
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
conn = LINKED_LIST_ENTRY(lp, struct connection, link);
#define LINKED_LIST_FOREACH(H,N,T) \
for (N = (H)->next, T = (N)->next; N != (H); N = (T), T = (N)->next)

该函数中有三处使用到这种遍历server->active_connections并取得struct connection *类型的指针的地方:
  • add_to_set for socket select
  • Read/write from clients
  • Close expired connections and those that need to be closed
其中,close_conn(conn);时,会执行下述语句来移除server->active_connections列表中对应的节点:
LINKED_LIST_REMOVE(&conn->link);

5. 从链表中移除节点

从代码上看,只有在close_conn函数中才有调用到 LINKED_LIST_REMOVE(&conn->link)操作,但是close_conn函数只在mg_poll_server中有调用。
#define LINKED_LIST_REMOVE(N) do { ((N)->next)->prev = ((N)->prev); \
((N)->prev)->next = ((N)->next); LINKED_LIST_INIT(N); } while (0)

LINKED_LIST_REMOVE宏完成了将conn从server->active_connections列表中移除的功能。
然后closesocket(conn->client_sock)并依次free掉conn结构体中申请的空间以及最后free掉自身。

mg_destroy_server函数中,只是遍历所有的active_connections,然后free掉conn,但是并没有完成其它释放操作,如closesocket(conn->client_sock)、free(conn->request)等等,因此,此处需要改为也调用close_conn(conn)来完成所有的释放和销毁动作。

6. close_conn函数调用处分析

既然server->active_connections链表实际上是用来保存struct connection *数据的,那么要了解什么时候释放节点,则必须分析close_conn(conn)的调用处。
调用close_conn函数的地方有三处:
mg_poll_server函数:
else if (conn->flags & CONN_CLOSE) {
close_conn(conn);
}
如果conn->flags标记中包含CONN_CLOSE,那么轮询处理中就会关闭该connection。

mg_poll_server函数:
if (conn->flags & CONN_CLOSE || conn->last_activity_time < expire_time) {
close_conn(conn);
}
如果conn->flags标记中包含CONN_CLOSE或者连接的最后活动时间小于失效时间,则轮询处理中也会关闭该connection。

mg_destroy_server函数:
close_conn(conn);
此处本来就是要销毁server,调用close_conn来关闭和销毁资源。

6.1 conn->flags中CONN_CLOSE标记

write_to_client函数:
int n = conn->ssl == NULL ? send(conn->client_sock, io->buf, io->len, 0) : 0;
if (is_error(n)) {
conn->flags |= CONN_CLOSE;
}
如果发送数据失败,置conn->flags中CONN_CLOSE标记

write_to_client函数:
if (io->len == 0 && conn->flags & CONN_SPOOL_DONE) {
conn->flags |= CONN_CLOSE;
}
如果待发送数据为0(亦即发送数据完毕),并且CONN_SPOOL_DONE标记也为true,置conn->flags中CONN_CLOSE标记

read_from_client函数:
n = recv(conn->client_sock, buf, sizeof(buf), 0);
if (is_error(n)) {
conn->flags |= CONN_CLOSE;
}
如果接收数据失败,置conn->flags中CONN_CLOSE标记

close_local_endpoint函数:
if (keep_alive) {
process_request(conn); // Can call us recursively if pipelining is used
} else {
conn->flags |= conn->remote_iobuf.len == 0 ? CONN_CLOSE : CONN_SPOOL_DONE;
}
如果不是keep_alive,并且数据已经发送完毕,置conn->flags中CONN_CLOSE标记

6.2 conn->flags中CONN_SPOOL_DONE标记

open_file_endpoint函数:
if (!strcmp(conn->mg_conn.request_method, "HEAD")) {
conn->flags |= CONN_SPOOL_DONE;
close(conn->endpoint.fd);
conn->endpoint_type = EP_NONE;
}
如果HTTP请求方法是HEAD(类似于GET, 但是不返回body信息,用于检查对象是否存在,以及得到对象的元数据),则置conn->flags中CONN_SPOOL_DONE标记

send_options函数:
conn->flags |= CONN_SPOOL_DONE;
如果HTTP请求方法是OPTIONS(询问可以执行哪些方法),则置conn->flags中CONN_SPOOL_DONE标记

close_local_endpoint函数:
if (keep_alive) {
process_request(conn); // Can call us recursively if pipelining is used
} else {
conn->flags |= conn->remote_iobuf.len == 0 ? CONN_CLOSE : CONN_SPOOL_DONE;
}
如果不是keep_alive,并且数据尚未发送完毕,置conn->flags中CONN_SPOOL_DONE标记

综上分析,当HTTP HEAD或HTTP OPTIONS请求,或者需要进行半关闭(HTTP 300重定向或数据读取出错等等),如果这个时候数据没有传输完毕,需要置CONN_SPOOL_DONE标记
//TODO //FIXME
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值