event 信号初始化

上一节我们对信号evsignal_info作了一个简单的分析, 本节就如同前面分析event功能函数时一样的来分析signal操作.

信号初始化

evsignal_init函数实现对一个信号的初始化操作. 源码位置: signal.c

int
evsignal_init(struct event_base *base)
{
	int i;

	/* 
	 * Our signal handler is going to write to one end of the socket
	 * pair to wake up our event loop.  The event loop then scans for
	 * signals that got delivered.
	 */
	// 创建套接字
	// 将 ev_signal_pair[2] 一对用 accept 和 connect 关联, 用于响应事件
	if (evutil_socketpair(
		    AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {
#ifdef WIN32
		/* Make this nonfatal on win32, where sometimes people
		   have localhost firewalled. */
		event_warn("%s: socketpair", __func__);
#else
		event_err(1, "%s: socketpair", __func__);
#endif
		return -1;
	}

	// 设置文件描述符
	FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]);
	FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]);
	// 初始化 ev_signal
	base->sig.sh_old = NULL;
	base->sig.sh_old_max = 0;
	base->sig.evsignal_caught = 0;
	memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG);
	/* initialize the queues for all events */
	for (i = 0; i < NSIG; ++i)
		TAILQ_INIT(&base->sig.evsigevents[i]);

	// 将写事件设置为 非阻塞
        evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]);

	// 注册读事件并设置为永久事件, 设置回调函数为 evsignal_cb
	// evsignal_cb : 实现读一个字节. 实质表示有信号可以处理
	event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1],
		EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal);
	base->sig.ev_signal.ev_base = base;
	base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;    // 标志已被初始化

	return 0;
}

函数中可能不太明白的

  1. FD_CLOSEONEXEC, 下面给出了源码, 其实就是设置文件描述符fork后会自动关闭.

    #define FD_CLOSEONEXEC(x) do { \
            if (fcntl(x, F_SETFD, 1) == -1) \
                    event_warn("fcntl(%d, F_SETFD)", x); \
    } while (0)
    
  2. evutil_make_socket_nonblocking也是相似的功能, 将套接字设为非阻塞.

  3. 唯一不太明了我猜就是在信号初始化中居然调用evutil_socketpair函数创建套接字, 这个问题待会结合下面的函数来说明.

信号初始化默认设置了一个事件的回调函数evsignal_cb, 该函数的功能也很简单, 从fd(也就是ev_signal_pair[1])读一个字节.

/* Callback for when the signal handler write a byte to our signaling socket */
static void
evsignal_cb(int fd, short what, void *arg)
{
        static char signals[1];
#ifdef WIN32
        SSIZE_T n;
#else
        ssize_t n;
#endif

        n = recv(fd, signals, sizeof(signals), 0);
        if (n == -1)
                event_err(1, "%s: read", __func__);
}
evutil_socketpair 函数

该函数我们做注释, 因为都是网络编程那方面的知识, 主要关注的是fd[2]这个参数.

int
evutil_socketpair(int family, int type, int protocol, int fd[2])
{
#ifndef WIN32
	return socketpair(family, type, protocol, fd);
#else
	/* This code is originally from Tor.  Used with permission. */

	/* This socketpair does not work when localhost is down. So
	 * it's really not the same thing at all. But it's close enough
	 * for now, and really, when localhost is down sometimes, we
	 * have other problems too.
	 */
	int listener = -1;
	int connector = -1;
	int acceptor = -1;
	struct sockaddr_in listen_addr;
	struct sockaddr_in connect_addr;
	int size;
	int saved_errno = -1;

	if (protocol
#ifdef AF_UNIX
		|| family != AF_UNIX
#endif
		) {
		EVUTIL_SET_SOCKET_ERROR(WSAEAFNOSUPPORT);
		return -1;
	}
	if (!fd) {
		EVUTIL_SET_SOCKET_ERROR(WSAEINVAL);
		return -1;
	}

	listener = socket(AF_INET, type, 0);
	if (listener < 0)
		return -1;
	memset(&listen_addr, 0, sizeof(listen_addr));
	listen_addr.sin_family = AF_INET;
	listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	listen_addr.sin_port = 0;	/* kernel chooses port.	 */
	if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr))
		== -1)
		goto tidy_up_and_fail;
	if (listen(listener, 1) == -1)
		goto tidy_up_and_fail;

	connector = socket(AF_INET, type, 0);
	if (connector < 0)
		goto tidy_up_and_fail;
	/* We want to find out the port number to connect to.  */
	size = sizeof(connect_addr);
	if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1)
		goto tidy_up_and_fail;
	if (size != sizeof (connect_addr))
		goto abort_tidy_up_and_fail;
	if (connect(connector, (struct sockaddr *) &connect_addr,
				sizeof(connect_addr)) == -1)
		goto tidy_up_and_fail;

	size = sizeof(listen_addr);
	acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);
	if (acceptor < 0)
		goto tidy_up_and_fail;
	if (size != sizeof(listen_addr))
		goto abort_tidy_up_and_fail;
	EVUTIL_CLOSESOCKET(listener);// 关闭监听套接字
	/* Now check we are talking to ourself by matching port and host on the
	   two sockets.	 */
	if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1)
		goto tidy_up_and_fail;
	if (size != sizeof (connect_addr)
		|| listen_addr.sin_family != connect_addr.sin_family
		|| listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr
		|| listen_addr.sin_port != connect_addr.sin_port)
		goto abort_tidy_up_and_fail;
    
    // 最重要的部分
	fd[0] = connector;
	fd[1] = acceptor;

	return 0;

 abort_tidy_up_and_fail:
	saved_errno = WSAECONNABORTED;
 tidy_up_and_fail:
	if (saved_errno < 0)
		saved_errno = WSAGetLastError();
	if (listener != -1)
		EVUTIL_CLOSESOCKET(listener);
	if (connector != -1)
		EVUTIL_CLOSESOCKET(connector);
	if (acceptor != -1)
		EVUTIL_CLOSESOCKET(acceptor);

	EVUTIL_SET_SOCKET_ERROR(saved_errno);
	return -1;
#endif
}

注意该函数的一个地方.

  • fd[0] = connect 表示的是保存服务端的套接字(文件描述符)
  • fd[1] = acceptor 表示的是保存客户端的套接字(文件描述符)
    // 最重要的部分
	fd[0] = connector;
	fd[1] = acceptor;
socket pair

上一节在evsignal_info结构中提到ev_signal_pair[2]是一个socket pair.

什么是socket pair呢?

  • 就是采用了IO通信的方式一对文件描述符组合. 回到ev_signal_pair[2], 这就是一对IO通信的连接, [0]表示服务端, [1]表示客服端.

信号机制为什么要用socket pair呢 ?

  • 当信号发生时, 注册的回调函数evsignal_handler会向服务端发送一字节数据并设置该信号标志和次数, 而服务端则调用统一的就绪回调函数evsignal_cb接收一字节数据, 而libevent通过分发器(如 : epoll 中的epoll_wait) 调用evsignal_process将信号加入就绪队列中. (是不是感觉很巧妙, 信号可以这样被加入就绪链表, 我第一次看到这里的时候也很震惊,神奇的操作) (evsignal_handler我们下节来分析)
  • libevent 之所以这样做的原因就是将信号, IO, 时间都交给统一的激活函数处理, 而不再是单干自己.

总结

  • 清楚evsignal_init的实现过程
  • 明白socket pair在信号处理中的作用
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值