skynet网络部分剖析(二) reactor模型

skynet是基于reactor网络模型的,并且他的网络层使用单线程。因为云风大大认为即使是代码量稍大一些的单线程程序,也会比代码量较小的多线程程序更容易理解,出 bug 的机会也更少。而且经典的网络服务程序,如 redis nginx 并没有因为单线程处理网络 IO 而变现得不堪,反而有不错的口碑。

既然是reactor模式,就必然有同步事件分离器Synchronous Event Demultiplexer,初始分派器Initiation Dispatcher,事件处理器的接口Event Handler。同步事件分离器负责等待新的事件发生。在linux网络模型中,有三种等待网络事件发生的模型,分别是select,poll,epoll。当然skynet选择了最为高效的epoll模型(关于epoll的理解可以参看我的这篇文章:epoll EPOLLL、EPOLLET模式与阻塞、非阻塞)。skynet没有严格的分派器,但是有事件处理器接口。处理epoll事件的函数为:

int 
socket_server_poll(struct socket_server *ss, struct socket_message * result, int * more) {
	for (;;) {
		if (ss->checkctrl) {
			if (has_cmd(ss)) {     //管道内是否有命令数据
				int type = ctrl_cmd(ss, result); //只有SOCKET_DATA,SOCKET_CLOSE,SOCKET_OPEN,SOCKET_ACCEPT等返回的不是-1 listen命令是
				if (type != -1) {
					clear_closed_event(ss, result, type);
					return type;
				} else
					continue;
			} else {
				ss->checkctrl = 0;
			}
		}
		if (ss->event_index == ss->event_n) {    //刚开始或者所有消息都已处理
			ss->event_n = sp_wait(ss->event_fd, ss->ev, MAX_EVENT);
			ss->checkctrl = 1;
			if (more) {
				*more = 0;
			}
			ss->event_index = 0;
			if (ss->event_n <= 0) {
				ss->event_n = 0;
				if (errno == EINTR) {
					continue;
				}
				return -1;
			}
		}
		struct event *e = &ss->ev[ss->event_index++];
		struct socket *s = e->s;
		if (s == NULL) {
			// dispatch pipe message at beginning
			continue;
		}
		struct socket_lock l;
		socket_lock_init(s, &l);
		switch (s->type) {
		case SOCKET_TYPE_CONNECTING:
			return report_connect(ss, s, &l, result);
		case SOCKET_TYPE_LISTEN: {                     //有客户端连接消息
			int ok = report_accept(ss, s, result);
			if (ok > 0) {
				return SOCKET_ACCEPT;
			} if (ok < 0 ) {
				return SOCKET_ERR;
			}
			// when ok == 0, retry
			break;
		}
		case SOCKET_TYPE_INVALID:
			fprintf(stderr, "socket-server: invalid socket\n");
			break;
		default:                        //一般是读,写,错误消息
			if (e->read) {
				int type;
				if (s->protocol == PROTOCOL_TCP) {                    //有tcp协议的数据触发
					type = forward_message_tcp(ss, s, &l, result);    
				} else {
					type = forward_message_udp(ss, s, &l, result);
					if (type == SOCKET_UDP) {
						// try read again
						--ss->event_index;
						return SOCKET_UDP;
					}
				}
				if (e->write && type != SOCKET_CLOSE && type != SOCKET_ERR) {
					// Try to dispatch write message next step if write flag set.
					e->read = false;
					--ss->event_index;
				}
				if (type == -1)
					break;				
				return type;
			}
			if (e->write) {
				int type = send_buffer(ss, s, &l, result);
				if (type == -1)
					break;
				return type;
			}
			if (e->error) {
				// close when error
				int error;
				socklen_t len = sizeof(error);  
				int code = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &error, &len);  
				const char * err = NULL;
				if (code < 0) {
					err = strerror(errno);
				} else if (error != 0) {
					err = strerror(error);
				} else {
					err = "Unknown error";
				}
				force_close(ss, s, &l, result);
				result->data = (char *)err;
				return SOCKET_ERR;
			}
			if(e->eof) {
				force_close(ss, s, &l, result);
				return SOCKET_CLOSE;
			}
			break;
		}
	}
}

当有网络事件触发时,我们会填充一个事件event的指针:

static int 
sp_wait(int efd, struct event *e, int max) {
	struct epoll_event ev[max];
	int n = epoll_wait(efd , ev, max, -1);
	int i;
	for (i=0;i<n;i++) {
		e[i].s = ev[i].data.ptr;
		unsigned flag = ev[i].events;
		e[i].write = (flag & EPOLLOUT) != 0;
		e[i].read = (flag & (EPOLLIN | EPOLLHUP)) != 0;
		e[i].error = (flag & EPOLLERR) != 0;
		e[i].eof = false;
	}

	return n;
}

epoll得到的data.ptr是在事先填充好的,他是在把socket fd纳入epoll监管中填充的,前篇文章已经讲了监听socket和连接socket纳入epoll监管的时机,参见:skynet网络部分剖析(一) socket的状态

得到了事件event,根据event是否可读可写或者有错误,我们就好处理事件了。这里没有像其他项目一样调用回调函数,而是就地调用处理函数。例如有tcp数据时调用forward_message_tcp。但是处理写数据却和其他的项目不一样。

skynet是面向lua脚本写逻辑的。lua逻辑调用写时socket.write(id, buf)会先发送到管道中,与epoll处理数据不在一个线程里。使用管道的好处是,其他线程向socket线程发送数据包,socket线程能够像处理网络消息一样,处理来自其他线程的请求,并且完全不用加任何锁,保证了线程安全,也简化了逻辑复杂度。当然管道也在epoll的监管之中。socket_server.c的ctrl_cmd函数就是用来处理管道消息的。当收到发送消息时,在这里处理了发送消息逻辑,只有当发送缓冲区满时才会保留发送缓存并添加可写的事件到epoll,这样epoll就可以处理发送缓存消息了。


参看:

https://blog.codingnow.com/2017/06/skynet_socket.html

https://blog.csdn.net/u010168160/article/details/53019039

https://www.jianshu.com/p/eef7ebe28673

猜你喜欢

转载自blog.csdn.net/zxm342698145/article/details/81020394