一个muduo的简单实现

前篇大致介绍了一下muduo,这篇探讨一下大致的代码实现。

muduo是基于反应堆reactor,且one loop per thread模式的。网络服务器两大类型的socket是监听socket和连接socket,我们可以在两种各自的线程里面对他们进行操作,每个线程里有事件循环来处理各种io事件。

我们知道reactor模式是基于事件回调的,为了处理好每个事件的回调,我们先构造一个事件结构体。他必包含事件对应的文件描述符fd,读写回调函数以及事件本身相关属性,如事件的类型,发生时间等等。

如何等待事件的到来,在linux上,最高效的网络模型必属epoll。又如何驱动事件呢?我们可以在每个线程里封装一个循环,专门负责事件的获取和分发。

有那么多网络事件,我们如何获取感兴趣的事件呢?例如对于监听socket,我们一般只获取连接事件,对于建立连接的socket,我们读写事件,甚至错误事件都得监听。在reactor模式里,这个过程就叫注册事件。

 

我们可以在创建事件的时候就注册。线程是事先创建好的,也可以利用线程池。注册事件是在某个线程上进行的。对于epoll,注册实际上是调用epoll_ctl函数进行添加。示例代码:

static void event_accept_callback(int listenfd, event *ev, void *arg)
{
	listener *ls = (listener *)arg;
	inet_address client_addr;
	socklen_t clilen = sizeof(client_addr.addr);
	int ret;
	int connfd;
	static int i;
	
	connfd = accept(listenfd, (struct sockaddr *)&client_addr.addr,	&clilen);
	if (connfd < 0)
	{
		debug_ret("file: %s, line: %d", __FILE__, __LINE__);
		
		int save = errno;
		if (save == EAGAIN || save == ECONNABORTED || save == EINTR
			|| save == EPROTO || save == EPERM || save == EMFILE)
		{
			return;
		}
		else
		{
			debug_sys("file: %s, line: %d", __FILE__, __LINE__);
		}
	}

	fcntl(connfd, F_SETFL, fcntl(connfd, F_GETFL) | O_NONBLOCK);

	int opt = 1;
	setsockopt(connfd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));

	/* epoll是线程安全的 */
	if (i == MAX_LOOP)
		i = 0;
	
	connection *conn = connection_create(loops[i], connfd, ls->readable_callback);
	if (conn == NULL)
	{
		debug_quit("file: %s, line: %d", __FILE__, __LINE__);
	}

	i++;
	
	/* 用户回调函数 */
	if (ls->new_connection_callback)
		ls->new_connection_callback(conn);

}

listener *listener_create(server_manager *manager, inet_address ls_addr,
						  connection_callback_pt read_cb, connection_callback_pt new_conn_cb)
{
	int ret;
	listener *ls;
	
	ls = malloc(sizeof(listener));
	if (ls == NULL)
	{
		debug_ret("file: %s, line: %d", __FILE__, __LINE__);
		return NULL;
	}
	
	ls->listen_addr = ls_addr;
	ls->readable_callback = read_cb;
	ls->new_connection_callback = new_conn_cb;

	/* 创建非阻塞套接字 */
	int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
	if (listen_fd < 0)
	{
		debug_ret("file: %s, line: %d", __FILE__, __LINE__);
		free(ls);
		return NULL;
	}

	int opt = 1;
	setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	ret = bind(listen_fd, (struct sockaddr *)&ls_addr.addr, sizeof(ls_addr.addr));
	if (ret < 0)
	{
		close(listen_fd);
		free(ls);
		return NULL;
	}

	ret = listen(listen_fd, SOMAXCONN);
	if (ret < 0)
	{
		debug_ret("file: %s, line: %d", __FILE__, __LINE__);
		close(listen_fd);
		free(ls);
		return NULL;
	}

    //创建监听事件
	ls->ls_event = event_create(listen_fd, EPOLLIN | EPOLLPRI,
								event_accept_callback, ls, NULL, NULL);
	if (ls->ls_event == NULL)
	{
		debug_ret("file: %s, line: %d", __FILE__, __LINE__);
		close(listen_fd);
		free(ls);
		return NULL;
	}
	
    
    //注册事件
	accept_event_add(manager, ls->ls_event);
	event_start(ls->ls_event);

	return ls;
}


connection *connection_create(event_loop *loop, int connfd, connection_callback_pt read_cb)
{
	connection *conn = malloc(sizeof(connection));
	if (conn == NULL)
	{
		return NULL;
	}

	conn->fd = connfd;
	conn->readable_callback = read_cb;
	
	/* 默认大小参考muduo */
	conn->input_buffer = array_create(1024, sizeof(char));
	if (conn->input_buffer == NULL)
	{
		debug_ret("file: %s, line: %d", __FILE__, __LINE__);
		free(conn);
		return NULL;
	}
	
	conn->output_buffer = array_create(1024, sizeof(char));
	if (conn->output_buffer == NULL)
	{
		array_free(conn->input_buffer);
		free(conn);
		return NULL;
	}

    //创建连接事件
	conn->conn_event = event_create(connfd, EPOLLIN | EPOLLPRI,
				event_readable_callback, conn, event_writable_callback, conn);
	if (conn->conn_event == NULL)
	{
		array_free(conn->input_buffer);
		array_free(conn->output_buffer);
		free(conn);
		return NULL;
	}

    //注册连接事件
	io_event_add(loop, conn->conn_event);
	
	/* 跟TCP相关的event才需要对应的connection */
	conn->conn_event->conn = conn;
	event_start(conn->conn_event);


	return conn;
}

这里可以在主线程中循环处理监听socket的事件以及其他诸如定时器等事件,在其他线程里处理连接socket的事件,最终他们都会如下调用:

struct timeval epoller_dispatch(int epoll_fd, int timeout)
{
	int i;
	event *ev;
	struct epoll_event events[MAX_EVENTS];
	struct timeval now;

	/* 就绪事件在epoll内部排队,当就绪事件大于MAX_EVENTS时
	 * 剩余就绪事件可在下次epoll_wait时获得
	 */
	int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, timeout);
	
	gettimeofday(&now, NULL);
	//debug_msg("tid = %ld", (long int)gettid());

	if (nfds == 0)
	{
		/* timeout */
	}
	
	/* 取出event对象 */
	for (i = 0; i < nfds; i++)
	{
		ev = events[i].data.ptr;
		ev->time = now;
		ev->actives = events[i].events;
		event_handler(ev);
	}
	
	return now;
}

 本质上,上面就是reactor模式的典型写法,然后加入了多线程处理。muduo也是上面的思路,只不过是面向对象的思想,把每个实体都抽象为一个对象。例如Acceptor,Connector对象都包含Channel对象,他是一条具体连接的抽象。

 

代码在git上,赏个星星

https://github.com/shonm520/mu_event

 

欢迎加入QQ群 858791125 讨论skynet,游戏后台开发,lua脚本语言等问题。

 

 

 

 

 

 

 

 

你可能感兴趣的:(muduo,源码分析)