Listener fd的创建以及监听代码位于[src/proto_tcp.c]的tcp_bind_listener函数中。
[src/proto_tcp.c] tcp_bind_listener()
int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
{
__label__ tcp_return, tcp_close_return;
int fd, err;
const char *msg = NULL;
/* ensure we never return garbage */
if (errmsg && errlen)
*errmsg = 0;
if (listener->state != LI_ASSIGNED)
return ERR_NONE; /* already bound */
为什么对于listener->state != LI_ASSIGNED的情况下,它就是绑定的了呢?那么看下listener入队时候是怎么操作的。
[src/proto_tcp.c] tcpv4_add_listener()
/* Add listener to the list of tcpv4 listeners. The listener's state
* is automatically updated from LI_INIT to LI_ASSIGNED. The number of
* listeners is updated. This is the function to use to add a new listener.
*/
void tcpv4_add_listener(struct listener *listener)
{
if (listener->state != LI_INIT)
return;
listener->state = LI_ASSIGNED;
listener->proto = &proto_tcpv4;
LIST_ADDQ(&proto_tcpv4.listeners, &listener->proto_list);
proto_tcpv4.nb_listeners++;
}
很明显,listener在入队时状态会自动改变成LI_ASSIGNED,因此对于处于队列中的listener,他们的状态至少为LI_ASSIGNED,也就是说,如果不为ASSIGNED,那么必定已经绑定过来了。对于已经绑定然后解绑定的listener,只要没有移出队列,那么其状态也是LI_ASSIGNED。如果从队列中移除,那么状态就要小于LI_ASSIGNED了,从添加的过程要求状态为LI_INIT,大致可以知道,其从队列删除时,状态会变成LI_INIT,事实上也是如此。
[src/proto_tcp.c] tcp_bind_listener()
err = ERR_NONE;
if ((fd = socket(listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
err |= ERR_RETRYABLE | ERR_ALERT;
msg = "cannot create listening socket";
goto tcp_return;
}
if (fd >= global.maxsock) {
err |= ERR_FATAL | ERR_ABORT | ERR_ALERT;
msg = "not enough free sockets (raise '-n' parameter)";
goto tcp_close_return;
}
if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
err |= ERR_FATAL | ERR_ALERT;
msg = "cannot make socket non-blocking";
goto tcp_close_return;
}
创建socket,然后判断数量是否超越最大值(作者很多地方都习惯于先分配再判断,这应该是与其维护内核代码相关的)。如果没有超出限制,那么将fd设置为非阻塞。
[src/proto_tcp.c] tcp_bind_listener()
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) == -1) {
/* not fatal but should be reported */
msg = "cannot do so_reuseaddr";
err |= ERR_ALERT;
}
if (listener->options & LI_O_NOLINGER)
setsockopt(fd, SOL_SOCKET, SO_LINGER, (struct linger *) &nolinger, sizeof(struct linger));
#ifdef SO_REUSEPORT
/* OpenBSD supports this. As it's present in old libc versions of Linux,
* it might return an error that we will silently ignore.
*/
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &one, sizeof(one));
#endif
#ifdef CONFIG_HAP_LINUX_TPROXY
if ((listener->options & LI_O_FOREIGN)
&& (setsockopt(fd, SOL_IP, IP_TRANSPARENT, (char *) &one, sizeof(one)) == -1)
&& (setsockopt(fd, SOL_IP, IP_FREEBIND, (char *) &one, sizeof(one)) == -1)) {
msg = "cannot make listening socket transparent";
err |= ERR_ALERT;
}
#endif
#ifdef SO_BINDTODEVICE
/* Note: this might fail if not CAP_NET_RAW */
if (listener->interface) {
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
listener->interface, strlen(listener->interface) + 1) == -1) {
msg = "cannot bind listener to device";
err |= ERR_WARN;
}
}
#endif
#if defined(TCP_MAXSEG)
if (listener->maxseg) {
if (setsockopt(fd, IPPROTO_TCP, TCP_MAXSEG,
&listener->maxseg, sizeof(listener->maxseg)) == -1) {
msg = "cannot set MSS";
err |= ERR_WARN;
}
}
#endif
#if defined(TCP_DEFER_ACCEPT)
if (listener->options & LI_O_DEF_ACCEPT) {
/* defer accept by up to one second */
int accept_delay = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &accept_delay, sizeof(accept_delay)) == -1) {
msg = "cannot enable DEFER_ACCEPT";
err |= ERR_WARN;
}
}
#endif
根据配置,设置socket的选项。其中要说明的是TCP_DEFER_ACCEPT,这个选项要求在backlog中的连接,需要有数据才会被应用程序接收。
[src/proto_tcp.c] tcp_bind_listener()
if (bind(fd, (struct sockaddr *)&listener->addr, listener->proto->sock_addrlen) == -1) {
err |= ERR_RETRYABLE | ERR_ALERT;
msg = "cannot bind socket";
goto tcp_close_return;
}
if (listen(fd, listener->backlog ? listener->backlog : listener->maxconn) == -1) {
err |= ERR_RETRYABLE | ERR_ALERT;
msg = "cannot listen to socket";
goto tcp_close_return;
}
#if defined(TCP_QUICKACK)
if (listener->options & LI_O_NOQUICKACK)
setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, (char *) &zero, sizeof(zero));
#endif
/* the socket is ready */
listener->fd = fd;
listener->state = LI_LISTEN;
绑定并监听,而且根据配置可选地关闭掉TCP_QUICKACK。此时listener的状态也要相应的调整为LI_LISTEN。
[src/proto_tcp.c] tcp_bind_listener()
/* the function for the accept() event */
fd_insert(fd);
fdtab[fd].cb[DIR_RD].f = listener->accept;
fdtab[fd].cb[DIR_WR].f = NULL; /* never called */
fdtab[fd].cb[DIR_RD].b = fdtab[fd].cb[DIR_WR].b = NULL;
fdtab[fd].owner = listener; /* reference the listener instead of a task */
fdtab[fd].state = FD_STLISTEN;
fdtab[fd].flags = FD_FL_TCP;
if (listener->options & LI_O_NOLINGER)
fdtab[fd].flags |= FD_FL_TCP_NOLING;
fdinfo[fd].peeraddr = NULL;
fdinfo[fd].peerlen = 0;
将fd插入fdtab中。并设置一些状态,以及回调函数。从此处可以看出,监听描述符对应的read函数来自于listener的accept成员,write函数为空,对于fdtab中的listner fd来说,他们的owner为对应的listner。Listner的accept成员被赋予event_accept函数,这是在解析配置文件的过程中进行的。待以后讲解main函数的时候再讲解。对于listener要求设置NOLINGER选项的,还需要加上程序本身的NOLINGER选项。
[proto/fd.h] fd_insert()
static inline void fd_insert(int fd)
{
fdtab[fd].ev = 0;
if (fd + 1 > maxfd)
maxfd = fd + 1;
}
Fd_insert的操作非常简单。将从poll中返回已处理事件置空,然后更新maxfd。
[src/proto_tcp.c] tcp_bind_listener()
tcp_return:
if (msg && errlen)
strlcpy2(errmsg, msg, errlen);
return err;
tcp_close_return:
close(fd);
goto tcp_return;
}
此处Listener fd的操作与常见的操作不一样的只有最后对于fdtab的处理。