五、listener fd的初始化

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函数来自于listeneraccept成员,write函数为空,对于fdtab中的listner fd来说,他们的owner为对应的listnerListneraccept成员被赋予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的处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值