lighttpd 源码分析1:网络模型

拿到lighttpd的源码就迫不及待的想去掉繁杂的皮肉以窥其简单的网络模型框架。我们平常所写的TCP网络服务程序离不开这样的步骤:新建socket ——》将socket绑定到某个地址——》侦听客户端连接——》accept获取已连接socket——》读写已连接socket。Lighttpd不外如此。

       lighttpd使用的是TCP预先派生子进程,每一个子进程各自accept的服务器设计范式,或者叫watcher-worker模型,关于各种网络程序设计范式在unix网络编程一书中有详细描述。整个程序的入口函数在server.c文件中,在main函数开始部分是各种繁杂的初始化工作,现在暂且略过,直接看到重点代码:

  1. /*当是以root用户运行程序时,调用network_init函数*/  
  2.     if(i_am_root)  
  3.     {     
  4.         …  
  5.         ...  
  6.         /* we need root-perms for port < 1024 */  
  7.         if (0 != network_init(srv)) {  
  8.                 plugins_free(srv);  
  9.                 server_free(srv);  
  10.                 return -1;  
  11.         }  
  12.         …  
  13.         ...  
  14.     }  
/*当是以root用户运行程序时,调用network_init函数*/
	if(i_am_root)
	{	
		…
		...
		/* we need root-perms for port < 1024 */
		if (0 != network_init(srv)) {
				plugins_free(srv);
				server_free(srv);
				return -1;
		}
		…
		...
	}

network_init定义在network.c中,起初也是各种初始化工作,最后调用network_server_init,我们假设运行平台是ipv4(代码针对不同socket类型有不同的流程,为了化繁为简,只看ipv4流程),看下network_server_init流程的关键代码:

  1. …  
  2. …  
  3. /*这是在创建socket*/  
  4. if (srv_socket->fd == -1) {  
  5.     srv_socket->addr.plain.sa_family = AF_INET;  
  6.     if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) {  
  7.         log_error_write(srv, __FILE__, __LINE__, "ss""socket failed:", strerror(errno));  
  8.         goto error_free_socket;  
  9.     }  
  10. }  
  11. …  
  12. …  
  13. /*这是在初始化socket地址*/  
  14. case AF_INET:  
  15.     memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in));  
  16.     srv_socket->addr.ipv4.sin_family = AF_INET;  
  17.     if (host == NULL) {  
  18.         srv_socket->addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY);  
  19.     } else {  
  20.         struct hostent *he;  
  21.         if (NULL == (he = gethostbyname(host))) {  
  22.             log_error_write(srv, __FILE__, __LINE__,  
  23.                     "sds""gethostbyname failed: ",  
  24.                     h_errno, host);  
  25.             goto error_free_socket;  
  26.         }  
  27.   
  28.         if (he->h_addrtype != AF_INET) {  
  29.             log_error_write(srv, __FILE__, __LINE__, "sd""addr-type != AF_INET: ", he->h_addrtype);  
  30.             goto error_free_socket;  
  31.         }  
  32.   
  33.         if (he->h_length != sizeof(struct in_addr)) {  
  34.             log_error_write(srv, __FILE__, __LINE__, "sd""addr-length != sizeof(in_addr): ", he->h_length);  
  35.             goto error_free_socket;  
  36.         }  
  37.   
  38.         memcpy(&(srv_socket->addr.ipv4.sin_addr.s_addr), he->h_addr_list[0], he->h_length);  
  39.     }  
  40.     srv_socket->addr.ipv4.sin_port = htons(port);  
  41.   
  42.     addr_len = sizeof(struct sockaddr_in);  
  43.   
  44.     break;  
  45. …  
  46. …  
  47. /*这是在绑定socket地址*/  
  48. if (0 != bind(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len)) {  
  49.     switch(srv_socket->addr.plain.sa_family) {  
  50.     case AF_UNIX:  
  51.         log_error_write(srv, __FILE__, __LINE__, "sds",  
  52.                 "can't bind to socket:",  
  53.                 host, strerror(errno));  
  54.         break;  
  55.     default:  
  56.         log_error_write(srv, __FILE__, __LINE__, "ssds",  
  57.                 "can't bind to port:",  
  58.                 host, port, strerror(errno));  
  59.         break;  
  60.     }  
  61.     goto error_free_socket;  
  62. }  
  63. …  
  64. …  
  65. /*这是在侦听*/  
  66.   
  67. if (-1 == listen(srv_socket->fd, 128 * 8)) {  
  68.     log_error_write(srv, __FILE__, __LINE__, "ss""listen failed: ", strerror(errno));  
  69.     goto error_free_socket;  
  70. }  
  71. …  
  72. …  
	…
	…
	/*这是在创建socket*/
	if (srv_socket->fd == -1) {
		srv_socket->addr.plain.sa_family = AF_INET;
		if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
			goto error_free_socket;
		}
	}
	…
	…
	/*这是在初始化socket地址*/
	case AF_INET:
		memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in));
		srv_socket->addr.ipv4.sin_family = AF_INET;
		if (host == NULL) {
			srv_socket->addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
		} else {
			struct hostent *he;
			if (NULL == (he = gethostbyname(host))) {
				log_error_write(srv, __FILE__, __LINE__,
						"sds", "gethostbyname failed: ",
						h_errno, host);
				goto error_free_socket;
			}

			if (he->h_addrtype != AF_INET) {
				log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype);
				goto error_free_socket;
			}

			if (he->h_length != sizeof(struct in_addr)) {
				log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length);
				goto error_free_socket;
			}

			memcpy(&(srv_socket->addr.ipv4.sin_addr.s_addr), he->h_addr_list[0], he->h_length);
		}
		srv_socket->addr.ipv4.sin_port = htons(port);

		addr_len = sizeof(struct sockaddr_in);

		break;
	…
	…
	/*这是在绑定socket地址*/
	if (0 != bind(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len)) {
		switch(srv_socket->addr.plain.sa_family) {
		case AF_UNIX:
			log_error_write(srv, __FILE__, __LINE__, "sds",
					"can't bind to socket:",
					host, strerror(errno));
			break;
		default:
			log_error_write(srv, __FILE__, __LINE__, "ssds",
					"can't bind to port:",
					host, port, strerror(errno));
			break;
		}
		goto error_free_socket;
	}
	…
	…
	/*这是在侦听*/
	
	if (-1 == listen(srv_socket->fd, 128 * 8)) {
		log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed: ", strerror(errno));
		goto error_free_socket;
	}
	…
	…

一直到此处,lighttpd走的都是我们熟悉的流程。再回到main函数,来看下main中最重要的部分:

  1. …  
  2. ...  
  3. /*父进程是watcher,fork出许多worker子进程,当子进程个数达到上限时,父进程进入等待*/  
  4. /*直到有子进程退出,父进程在while循环中运行中,一旦跳出while循环程序也结束了*/  
  5. /*子进程fork出老后跳出while,也就是后面代码都是子进程的流程。*/  
  6. /* start watcher and workers */  
  7.     num_childs = srv->srvconf.max_worker;  
  8.     if (num_childs > 0) {  
  9.         int child = 0;  
  10.         while (!child && !srv_shutdown && !graceful_shutdown) {  
  11.             if (num_childs > 0) {  
  12.                 switch (fork()) {  
  13.                 case -1:  
  14.                     return -1;  
  15.                 case 0:  
  16.                     child = 1;  
  17.                     break;  
  18.                 default:  
  19.                     num_childs--;  
  20.                     break;  
  21.                 }  
  22.             } else {  
  23.                 int status;  
  24.   
  25.                 if (-1 != wait(&status)) {  
  26.                     /** 
  27.                      * one of our workers went away 
  28.                      */  
  29.                     num_childs++;  
  30.                 } else {  
  31.                     switch (errno) {  
  32.                     case EINTR:  
  33.                         /** 
  34.                          * if we receive a SIGHUP we have to close our logs ourself as we don't 
  35.                          * have the mainloop who can help us here 
  36.                          */  
  37.                         if (handle_sig_hup) {  
  38.                             handle_sig_hup = 0;  
  39.   
  40.                             log_error_cycle(srv);  
  41.   
  42.                             /** 
  43.                              * forward to all procs in the process-group 
  44.                              * 
  45.                              * we also send it ourself 
  46.                              */             if (!forwarded_sig_hup) {  
  47.                                 forwarded_sig_hup = 1;  
  48.                                 kill(0, SIGHUP);  
  49.                             }  
  50.                         }  
  51.                         break;  
  52.                     default:  
  53.                         break;  
  54.                     }  
  55.                 }  
  56.             }  
  57.         }  
  58.   
  59.         /** 
  60.          * for the parent this is the exit-point 
  61.          */  
  62.         if (!child) {  
  63.             /** 
  64.              * kill all children too 
  65.              */  
  66.             if (graceful_shutdown) {  
  67.                 kill(0, SIGINT);  
  68.             } else if (srv_shutdown) {  
  69.                 kill(0, SIGTERM);  
  70.             }  
  71.   
  72.             log_error_close(srv);  
  73.             network_close(srv);  
  74.             connections_free(srv);  
  75.             plugins_free(srv);  
  76.             server_free(srv);  
  77.             return 0;  
  78.         }  
  79.     }  
  80. …  
  81. …  
…
...
/*父进程是watcher,fork出许多worker子进程,当子进程个数达到上限时,父进程进入等待*/
/*直到有子进程退出,父进程在while循环中运行中,一旦跳出while循环程序也结束了*/
/*子进程fork出老后跳出while,也就是后面代码都是子进程的流程。*/
/* start watcher and workers */
	num_childs = srv->srvconf.max_worker;
	if (num_childs > 0) {
		int child = 0;
		while (!child && !srv_shutdown && !graceful_shutdown) {
			if (num_childs > 0) {
				switch (fork()) {
				case -1:
					return -1;
				case 0:
					child = 1;
					break;
				default:
					num_childs--;
					break;
				}
			} else {
				int status;

				if (-1 != wait(&status)) {
					/**
					 * one of our workers went away
					 */
					num_childs++;
				} else {
					switch (errno) {
					case EINTR:
						/**
						 * if we receive a SIGHUP we have to close our logs ourself as we don't
						 * have the mainloop who can help us here
						 */
						if (handle_sig_hup) {
							handle_sig_hup = 0;

							log_error_cycle(srv);

							/**
							 * forward to all procs in the process-group
							 *
							 * we also send it ourself
							 */				if (!forwarded_sig_hup) {
								forwarded_sig_hup = 1;
								kill(0, SIGHUP);
							}
						}
						break;
					default:
						break;
					}
				}
			}
		}

		/**
		 * for the parent this is the exit-point
		 */
		if (!child) {
			/**
			 * kill all children too
			 */
			if (graceful_shutdown) {
				kill(0, SIGINT);
			} else if (srv_shutdown) {
				kill(0, SIGTERM);
			}

			log_error_close(srv);
			network_close(srv);
			connections_free(srv);
			plugins_free(srv);
			server_free(srv);
			return 0;
		}
	}
…
…

到此,我们知道父进程在固定端口上监听后预先fork了一定数量的子进程,子进程将会做什么呢?按照本文开头描述的应该是accept后读写socket了吧!看接下的代码是否如此:

  1. …  
  2. …  
  3. /*fdevent系统的初始化,fdevent在lighttpd中主要处理各种IO事件,lighttpd采用的*/  
  4. /*是reactor模式,也就是多路复用加非阻塞式IO,而多路复用在各种平台上有差异,fdevent*/  
  5. /*通过OO的方法封装了各个不同实现,以使得代码中可以使用统一的接口*/  
  6. if (NULL == (srv->ev = fdevent_init(srv, srv->max_fds + 1, srv->event_handler))) {  
  7.     log_error_write(srv, __FILE__, __LINE__,  
  8.             "s""fdevent_init failed");  
  9.     return -1;  
  10. }  
  11. /*注册srv中保存的socket到fdevent中*/  
  12. /* 
  13.  * kqueue() is called here, select resets its internals, 
  14.  * all server sockets get their handlers 
  15.  * 
  16.  * */  
  17. if (0 != network_register_fdevents(srv)) {  
  18.     plugins_free(srv);  
  19.     network_close(srv);  
  20.     server_free(srv);  
  21.   
  22.     return -1;  
  23. }  
  24. …  
  25. …  
	…
	…
	/*fdevent系统的初始化,fdevent在lighttpd中主要处理各种IO事件,lighttpd采用的*/
	/*是reactor模式,也就是多路复用加非阻塞式IO,而多路复用在各种平台上有差异,fdevent*/
	/*通过OO的方法封装了各个不同实现,以使得代码中可以使用统一的接口*/
	if (NULL == (srv->ev = fdevent_init(srv, srv->max_fds + 1, srv->event_handler))) {
		log_error_write(srv, __FILE__, __LINE__,
				"s", "fdevent_init failed");
		return -1;
	}
	/*注册srv中保存的socket到fdevent中*/
	/*
	 * kqueue() is called here, select resets its internals,
	 * all server sockets get their handlers
	 *
	 * */
	if (0 != network_register_fdevents(srv)) {
		plugins_free(srv);
		network_close(srv);
		server_free(srv);

		return -1;
	}
	…
	…

函数network_register_fdevents在network.c中定义,代码如下:

  1. int network_register_fdevents(server *srv) {  
  2.     size_t i;  
  3.     /*清除fdevent的IO句柄,如同select的FD_ZERO清除fd set*/  
  4.     if (-1 == fdevent_reset(srv->ev)) {  
  5.         return -1;  
  6.     }  
  7.   
  8.     /* register fdevents after reset */  
  9.     for (i = 0; i < srv->srv_sockets.used; i++) {  
  10.         server_socket *srv_socket = srv->srv_sockets.ptr[i];  
  11.         //注册回调函数   
  12.         //一旦srv_socket->fd就绪,则触发函数 network_server_handle_fdevent   
  13.         fdevent_register(srv->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket);  
  14.         //告诉fdevent观察srv_socket->fd,一旦可读,则调用相应回调函数。   
  15.         fdevent_event_set(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);  
  16.     }  
  17.     return 0;  
  18. }  
int network_register_fdevents(server *srv) {
	size_t i;
	/*清除fdevent的IO句柄,如同select的FD_ZERO清除fd set*/
	if (-1 == fdevent_reset(srv->ev)) {
		return -1;
	}

	/* register fdevents after reset */
	for (i = 0; i < srv->srv_sockets.used; i++) {
		server_socket *srv_socket = srv->srv_sockets.ptr[i];
		//注册回调函数
		//一旦srv_socket->fd就绪,则触发函数 network_server_handle_fdevent
		fdevent_register(srv->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket);
		//告诉fdevent观察srv_socket->fd,一旦可读,则调用相应回调函数。
		fdevent_event_set(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
	}
	return 0;
}

这里的srv_socket->fd其实就是之前创建的监听套接字,至此,我们假设有一个客户连接请求过来,这时子进程的srv_socket->fd 可读,回调函数network_server_handle_fdevent被调用:

  1. static handler_t network_server_handle_fdevent(server *srv, void *context, int revents) {  
  2.     …  
  3.     ...  
  4.     /* accept()s at most 100 connections directly 
  5.      * 
  6.      * we jump out after 100 to give the waiting connections a chance */  
  7.     for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {  
  8.         handler_t r;  
  9.   
  10.         connection_state_machine(srv, con);  
  11.   
  12.         switch(r = plugins_call_handle_joblist(srv, con)) {  
  13.         case HANDLER_FINISHED:  
  14.         case HANDLER_GO_ON:  
  15.             break;  
  16.         default:  
  17.             log_error_write(srv, __FILE__, __LINE__, "d", r);  
  18.             break;  
  19.         }  
  20.     }  
  21.     return HANDLER_GO_ON;  
  22. }  
  23. connection_accept在connections.c中定义,代码简化为如下:  
  24.     …  
  25.     …  
  26.     //获取已连接套接字   
  27.     if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *) &cnt_addr, &cnt_len))) {  
  28.         switch (errno) {  
  29.         case EAGAIN:  
  30. #if EWOULDBLOCK != EAGAIN   
  31.         case EWOULDBLOCK:  
  32. #endif   
  33.         case EINTR:  
  34.             /* we were stopped _before_ we had a connection */  
  35.         case ECONNABORTED: /* this is a FreeBSD thingy */  
  36.             /* we were stopped _after_ we had a connection */  
  37.             break;  
  38.         case EMFILE:  
  39.             /* out of fds */  
  40.             break;  
  41.         default:  
  42.             log_error_write(srv, __FILE__, __LINE__, "ssd""accept failed:", strerror(errno), errno);  
  43.         }  
  44.         return NULL;  
  45.     }  
  46.     …  
  47.     …  
  48.     con->fd = cnt;  
  49.     con->fde_ndx = -1;  
  50.     //在fdevent中注册已连接socket : con->fd的回调函数connection_handle_fdevent   
  51.     fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);  
  52.     …  
  53.     …  
  54.     //设置一些属性,比如将con->fd设置为非阻塞的   
  55.     if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) {  
  56.             log_error_write(srv, __FILE__, __LINE__, "ss""fcntl failed: ", strerror(errno));  
  57.             return NULL;  
  58.         }  
  59.     …  
  60.     …  
static handler_t network_server_handle_fdevent(server *srv, void *context, int revents) {
	…
	...
	/* accept()s at most 100 connections directly
	 *
	 * we jump out after 100 to give the waiting connections a chance */
	for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {
		handler_t r;

		connection_state_machine(srv, con);

		switch(r = plugins_call_handle_joblist(srv, con)) {
		case HANDLER_FINISHED:
		case HANDLER_GO_ON:
			break;
		default:
			log_error_write(srv, __FILE__, __LINE__, "d", r);
			break;
		}
	}
	return HANDLER_GO_ON;
}
connection_accept在connections.c中定义,代码简化为如下:
	…
	…
	//获取已连接套接字
	if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *) &cnt_addr, &cnt_len))) {
		switch (errno) {
		case EAGAIN:
#if EWOULDBLOCK != EAGAIN
		case EWOULDBLOCK:
#endif
		case EINTR:
			/* we were stopped _before_ we had a connection */
		case ECONNABORTED: /* this is a FreeBSD thingy */
			/* we were stopped _after_ we had a connection */
			break;
		case EMFILE:
			/* out of fds */
			break;
		default:
			log_error_write(srv, __FILE__, __LINE__, "ssd", "accept failed:", strerror(errno), errno);
		}
		return NULL;
	}
	…
	…
	con->fd = cnt;
	con->fde_ndx = -1;
	//在fdevent中注册已连接socket : con->fd的回调函数connection_handle_fdevent
	fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);
	…
	…
	//设置一些属性,比如将con->fd设置为非阻塞的
	if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
			return NULL;
		}
	…
	…

分析到了这个地方,lighttpd的网络模型框架大致清楚了,正如文首所述,它和所有网络服务器程序一样都要走socket->bind->listen->accept流程,更具体的说,它使用了预先创建子进程,各子进程各自accept的范式,在UNIX网络编程中说这种范式会有accept惊群的问题,即当监听套接字可读,所有accept的子进程都会醒过来,但是只有一个进程获得已连接套接字,所有进程都唤醒是没有必要的,这样影响效率。对于这个问题,lighttpd似乎并没有处理。但是在新的linux内核中已经不存在accept惊群现象了。不过对于多路复用函数如select,epoll仍然存在类似问题,而代码里时常是先调epoll(select),再accept,lighttpd就是如此,因此还是会有新的惊群现象需要处理。如果不是我遗漏了的话,我没有发现lighttpd有相关代码对此进行处理,而nginx却有相关处理。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值