void free_engine(engine_t * e) { assert(e); fdevent_free(e->_fdevents); //释放事件结构体 free(e->socket_actived_list); //释放socket_t循环链表 free(e); //是否engine_t }
static void server_free(server *srv) { size_t i; for (i = 0; i < FILE_CACHE_MAX; i++) { buffer_free(srv->mtime_cache[i].str); } #define CLEAN(x) \ buffer_free(srv->x); CLEAN(response_header); CLEAN(parse_full_path); CLEAN(ts_debug_str); CLEAN(ts_date_str); CLEAN(errorlog_buf); CLEAN(response_range); CLEAN(tmp_buf); CLEAN(empty_string); CLEAN(cond_check_buf); CLEAN(srvconf.errorlog_file); CLEAN(srvconf.breakagelog_file); CLEAN(srvconf.groupname); CLEAN(srvconf.username); CLEAN(srvconf.changeroot); CLEAN(srvconf.bindhost); CLEAN(srvconf.event_handler); CLEAN(srvconf.pid_file); CLEAN(srvconf.modules_dir); CLEAN(srvconf.network_backend); CLEAN(tmp_chunk_len); #undef CLEAN #if 0 fdevent_unregister(srv->ev, srv->fd); #endif fdevent_free(srv->ev); free(srv->conns); if (srv->config_storage) { for (i = 0; i < srv->config_context->used; i++) { specific_config *s = srv->config_storage[i]; if (!s) continue; buffer_free(s->document_root); buffer_free(s->server_name); buffer_free(s->server_tag); buffer_free(s->ssl_pemfile); buffer_free(s->ssl_ca_file); buffer_free(s->ssl_cipher_list); buffer_free(s->ssl_dh_file); buffer_free(s->ssl_ec_curve); buffer_free(s->error_handler); buffer_free(s->errorfile_prefix); array_free(s->mimetypes); buffer_free(s->ssl_verifyclient_username); #ifdef USE_OPENSSL SSL_CTX_free(s->ssl_ctx); #endif free(s); } free(srv->config_storage); srv->config_storage = NULL; } #define CLEAN(x) \ array_free(srv->x); CLEAN(config_context); CLEAN(config_touched); CLEAN(status); CLEAN(srvconf.upload_tempdirs); #undef CLEAN joblist_free(srv, srv->joblist); fdwaitqueue_free(srv, srv->fdwaitqueue); if (srv->stat_cache) { stat_cache_free(srv->stat_cache); } array_free(srv->srvconf.modules); array_free(srv->split_vals); #ifdef USE_OPENSSL if (srv->ssl_is_init) { CRYPTO_cleanup_all_ex_data(); ERR_free_strings(); ERR_remove_state(0); EVP_cleanup(); } #endif free(srv); }
int main(int argc,char **argv) { server *srv=NULL; //step 1 : initialize server if( (srv= server_init()) ==NULL){ fprintf(stderr,"failed to initialize server in [%s|%d|%s]\n",__FILE__,__LINE__,__FUNCTION__); return -1; } //step 2 : parameter parse char opt_chr; while( (opt_chr=getopt(argc,argv,"f:hvD"))!=-1 ){ switch(opt_chr){ /*configuration file path */ case 'f':{ buffer_copy_string(srv->config->minihttpd_global_config_filepath,optarg); break; } /* show help */ case 'h':{ print_help(); server_free(srv); return 0; } case 'v':{ fprintf(stdout,"%s-%s",srv->config->service_name->ptr,srv->config->version_info->ptr); server_free(srv); return 0; } case 'D':{ srv->dont_daemonize=1; break; } default:{ print_help(); server_free(srv); return -1; } } } //step 3 :check if all configuraiton is legal if(buffer_is_empty(srv->config->service_root_dir)){ fprintf(stderr,"[%s|%d|%s]:please specify minihttp root dir in configuration file\n", __FILE__,__LINE__,__FUNCTION__); server_free(srv); return -1; } /*parse the mime configuration file */ if(buffer_is_empty(srv->config->mimetype_filepath) || (srv->config->table= mime_table_initialize( (const char*)srv->config->mimetype_filepath->ptr) )==NULL){ fprintf(stderr,"invalid mime configuration file is specified,pls check it..\n"); server_free(srv); return -1; } //step4 :server started srv->uid=getuid(); srv->gid=getgid(); if(srv->uid==0) { //we are root struct rlimit res_limit; if(getrlimit(RLIMIT_NOFILE,&res_limit)!=0){ fprintf(stderr,"[%s|%d|%s]: failed to get file descriptor max number for current process!\n", __FILE__,__LINE__,__FUNCTION__); server_free(srv); return -1; } res_limit.rlim_cur=srv->config->max_fd; res_limit.rlim_max=srv->config->max_fd; if(setrlimit(RLIMIT_NOFILE,&res_limit)!=0){ fprintf(stderr,"[%s|%d|%s]: failed call setrlimit(RLIMIT_NOFILE,) for current process!\n", __FILE__,__LINE__,__FUNCTION__); server_free(srv); return -1; } }else{ struct rlimit res_limit; if(getrlimit(RLIMIT_NOFILE,&res_limit)!=0){ fprintf(stderr,"[%s|%d|%s]: failed to get file descriptor max number for current process!\n", __FILE__,__LINE__,__FUNCTION__); server_free(srv); return -1; } if(srv->config->max_fd< res_limit.rlim_cur) res_limit.rlim_cur=srv->config->max_fd; else if(srv->config->max_fd<=res_limit.rlim_max) res_limit.rlim_cur=srv->config->max_fd; if(setrlimit(RLIMIT_NOFILE,&res_limit)!=0){ fprintf(stderr,"[%s|%d|%s]: failed call setrlimit(RLIMIT_NOFILE,) for current process!\n", __FILE__,__LINE__,__FUNCTION__); server_free(srv); return -1; } } //step 5: become a daemon process if dont_daemonize=0; if(!srv->dont_daemonize){ daemonize((const char*)srv->config->service_name->ptr); } //step 6: open log file for error log, by default we use syslog. // if the minihttpd log filepath is specified manually or server dont_daemonize=1, // we set mode = LOG_MODE_FILE; if(!buffer_is_empty(srv->config->log_filename) || srv->dont_daemonize ){ if(buffer_is_empty(srv->config->log_filename)) buffer_copy_string(srv->config->log_filename,MINIHTTPD_DEFAULT_LOG_FILEPATH); srv->log_fd= open((const char*)srv->config->log_filename->ptr,O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if(srv->log_fd<0){ server_free(srv); return -1; } fd_close_on_exec(srv->log_fd); srv->mode=server::LOG_MODE_FILE; } log_to_backend(srv,MINIHTTPD_LOG_LEVEL_INFO,"%s is start now...",(const char*)srv->config->service_name->ptr); //step 7 : create listening tcp socket(we only support ipv4 now) struct sockaddr_in * addr= (struct sockaddr_in*)&srv->server_addr; memset(addr,0,sizeof(*addr)); addr->sin_family=AF_INET; addr->sin_addr.s_addr=htonl(INADDR_ANY); addr->sin_port=htons(srv->config->listenint_port); srv->listening_socket_fd= create_tcp_socket((struct sockaddr*)addr,srv->config->max_listening_number); if(srv->listening_socket_fd<0){ log_to_backend(srv, MINIHTTPD_LOG_LEVEL_ERROR,"failed to create listening tcp socket on port:%d", srv->config->listenint_port); server_free(srv); return -1; } /* step 8: setup signal handler signo: SIGCHLD signo: SIGPIPE: unix domain socket pipe is broken signo: SIGINT: user intend to shutdown the minihttpd server if SIGINT is kill to the server proces for twice, the minihtpd server is going to shutdown signo: SIGTERM: exit minihttpd service now */ struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags=0; act.sa_handler=signal_handler; sigaction(SIGPIPE,&act,NULL); sigaction(SIGCHLD,&act,NULL); sigaction(SIGINT,&act,NULL); sigaction(SIGTERM,&act,NULL); /* step 9: fork worker child process and transfter accept socket file descriptor to worker process the only tasks for main processis : 1) to call accept to wait for connection and pick one worker process to handle the connection 2) wait all worker process to finish before exit. */ for(uint32_t worker_process_id=0;worker_process_id< srv->worker_number ;worker_process_id++) { server_child * child= &srv->child[worker_process_id]; //create unix domain socket if(socketpair(AF_UNIX,SOCK_STREAM,0,child->unix_domain_socket_fd)<0){ log_to_backend(srv,MINIHTTPD_LOG_LEVEL_ERROR,"failed to create unix domain socket for worker%d", worker_process_id); close(srv->listening_socket_fd); server_free(srv); return -1; } child->sent_connection_number=0; int unix_domain_socket_child_fd=child->unix_domain_socket_fd[1]; fd_set_nonblocking(unix_domain_socket_child_fd); child->pid=fork(); if(child->pid <0){ //we can not fork worker process, this should not be happened close(srv->listening_socket_fd); server_free(srv) ; return -1; } else if(child->pid ==0) { /* worker process */ /*we should use p_worker only in the child worker process */ #if 0 minihttpd_running_log(srv->log_fd,MINIHTTPD_LOG_LEVEL_INFO,__FILE__,__LINE__,__FUNCTION__, "worker(pid=%d) is starting.....",getpid()); #endif worker * server_worker = (worker*)malloc(sizeof(worker)); memset(server_worker,0,sizeof(worker)); server_worker->worker_id= worker_process_id; server_worker->unix_domain_socekt_fd=unix_domain_socket_child_fd; server_worker->log_filepath=buffer_init(); server_worker->global_config= srv->config; /*step1 : get current file descriptor max number (it should be same as parent process which we have set the resouces)*/ struct rlimit limit; if(getrlimit(RLIMIT_NOFILE,&limit)<0){ exit(-1); // terminated the worker } //close unnecessary file descriptor for(uint32_t file_descriptor_index=0;file_descriptor_index< limit.rlim_cur;file_descriptor_index++){ if(file_descriptor_index> STDERR_FILENO && file_descriptor_index != unix_domain_socket_child_fd){ close(file_descriptor_index); } } //step 2: set event handler server_worker->ev= fdevent_initialize(limit.rlim_cur); /*support max connection number */ uint32_t worker_support_max_connections=limit.rlim_cur/2; worker_connection_initialize(server_worker, worker_support_max_connections); //step 3 : register unix domain socket event fdevents_register_fd(server_worker->ev,server_worker->unix_domain_socekt_fd, unix_domain_socket_handle,server_worker); //EPOLLHUP |EPOLLERR events is set by default fdevents_set_events(server_worker->ev,server_worker->unix_domain_socekt_fd,EPOLLIN); //step 4 : open log file for worker to log debug/info/warning/error if(buffer_is_empty(server_worker->log_filepath)){ char worker_log_filepath[255]; snprintf(worker_log_filepath,sizeof(worker_log_filepath), MINIHTTPD_WORKER_CONFIG_PATH"%u.log", server_worker->worker_id ); buffer_append_string(server_worker->log_filepath,worker_log_filepath); } server_worker->log_fd= open((const char*)server_worker->log_filepath->ptr, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if(server_worker->log_fd<0){ exit(-2); } //step 5 : setup timer and expect timer will expire with internal 1 seconds time(&server_worker->cur_ts); int timer_fd=timerfd_create(CLOCK_REALTIME,TFD_NONBLOCK); struct itimerspec timer_spec; timer_spec.it_value.tv_sec=1; timer_spec.it_value.tv_nsec=0; timer_spec.it_interval.tv_sec=1; timer_spec.it_interval.tv_nsec=0; timerfd_settime(timer_fd,0,&timer_spec,NULL); // setup timer experation events handler fdevents_register_fd(server_worker->ev, timer_fd,worker_timer_expire_handler, server_worker); fdevents_set_events(server_worker->ev,timer_fd,EPOLLIN); /* main loop for worker: epoll event loop for unix domain socket and connections */ while(server_shutdown==0 || server_worker->cur_connection_number >0 ) { int n=epoll_wait(server_worker->ev->epoll_fd, server_worker->ev->epoll_events, server_worker->ev->max_epoll_events,-1); if(n<0 ){ if(errno!=EINTR){ minihttpd_running_log(server_worker->log_fd,MINIHTTPD_LOG_LEVEL_ERROR ,__FILE__,__LINE__,__FUNCTION__, "failed to call epoll with errno=%d",errno); } continue; } else if(n==0){ //we should not get to here continue; }else { for(uint32_t event_index=0;event_index<n;event_index++){ struct epoll_event * event= &server_worker->ev->epoll_events[event_index]; assert(event!=NULL); int connection_socket_fd= event->data.fd; event_handle handler=fdevents_get_handle(server_worker->ev,connection_socket_fd); void * event_ctx=fdevents_get_context(server_worker->ev, connection_socket_fd); assert(handler!=NULL); int handle_status= handler(connection_socket_fd,event_ctx,event->events); minihttpd_running_log(server_worker->log_fd,handle_status==0? MINIHTTPD_LOG_LEVEL_INFO:MINIHTTPD_LOG_LEVEL_ERROR, __FILE__,__LINE__,__FUNCTION__,"the epoll event is already handled!"); } } } minihttpd_running_log(server_worker->log_fd,MINIHTTPD_LOG_LEVEL_INFO, __FILE__,__LINE__,__FUNCTION__,"child worker process has finished all client requests!\n"); /*free all connections */ worker_free_connectons(server_worker); /* unregister timer file descriptor */ fdevents_unset_event(server_worker->ev,timer_fd); fdevents_unregister_fd(server_worker->ev,timer_fd); close(timer_fd); /* unregister unix domain socket hanlde events and handler context */ fdevents_unset_event(server_worker->ev,server_worker->unix_domain_socekt_fd); fdevents_unregister_fd(server_worker->ev,server_worker->unix_domain_socekt_fd); close(server_worker->unix_domain_socekt_fd); /* free fevents resources */ close(server_worker->ev->epoll_fd); fdevent_free(server_worker->ev); //close the log file close(server_worker->log_fd); buffer_free(server_worker->log_filepath); /* free worker */ free( (void*) server_worker); exit(0); //termianted the worker } //close the unix domain socket worker file descriptor; close(child->unix_domain_socket_fd[1]); // child worker is running child->worker_running=1; //parent process log_to_backend(srv,MINIHTTPD_LOG_LEVEL_INFO,"worker process %d is already created!",worker_process_id); } //main loop to accept client connection and re-transfter to worker process while(!server_shutdown) { /* log signal events after signal si handled by signal handler */ if(signal_pipe_handled){ /* if unix domain socket pipe is broken and we still write data to the pipe */ } if(signal_child_handled){ /*a child worker process has terminated */ int worker_exit_status; pid_t exit_worker_pid; while( (exit_worker_pid= waitpid(-1,&worker_exit_status,WNOHANG))>0){ if(WIFEXITED(worker_exit_status)){ log_to_backend(srv,MINIHTTPD_LOG_LEVEL_ERROR,"worker child process(pid=%d) has exited normally with exit" \ "status=%d",exit_worker_pid,WEXITSTATUS(worker_exit_status)); } else if(WIFSIGNALED(worker_exit_status)){ log_to_backend(srv,MINIHTTPD_LOG_LEVEL_ERROR,"worker child process(pid=%d) is killed by signal(%d)", exit_worker_pid,WTERMSIG(worker_exit_status)) } else{ log_to_backend(srv,MINIHTTPD_LOG_LEVEL_ERROR,"worker child process(pid=%d) has exited unexpected", exit_worker_pid); } //remove the worker from available worker list and do not send socket file descriptor to it for(uint32_t child_worker_id=0;child_worker_id< srv->worker_number;child_worker_id++){ if(srv->child[child_worker_id].pid==exit_worker_pid) srv->child[child_worker_id].worker_running=0; } } signal_child_handled=0; } //we block here to wait connection(only IPV4 is supported now ) struct sockaddr_in client_addr; socklen_t client_addr_length=sizeof(client_addr); int connection_fd =accept(srv->listening_socket_fd,(struct sockaddr*)&client_addr,(socklen_t*)& client_addr_length); if(connection_fd<0){ switch(errno){ case EINTR: // the connection is reset by client case ECONNABORTED: continue; case EMFILE: //file descriptor is all used now, need to send file descriptor to worker soon break; default: { log_to_backend(srv,MINIHTTPD_LOG_LEVEL_ERROR,"failed to call accept() with errno=%d\n",errno); break; } } } else{ /* pick up a worker process and send the @conneciton_fd to it the pick algorithm is round-robin,; but for the draft version, we just pick a worker that we has sent the min connections */ log_to_backend(srv,MINIHTTPD_LOG_LEVEL_INFO,"client connection is accepted,pick a worker to handle it."); uint32_t pick_worker_index= srv->worker_number; uint32_t min_sent_connections=0xFFFFFFFF; for(uint32_t worker_process_id=0; worker_process_id<srv->worker_number;worker_process_id++){ if(srv->child[worker_process_id].sent_connection_number < min_sent_connections && srv->child[worker_process_id].worker_running) { min_sent_connections= srv->child[worker_process_id].sent_connection_number; pick_worker_index= worker_process_id; } } if(pick_worker_index>= srv->worker_number){ /* we can not handle it as all child worker has exited...*/ close(connection_fd); continue; } /*set file descriptor to nonblocking and set close_on_exec flag*/ fd_set_nonblocking(connection_fd); fd_close_on_exec(connection_fd); if(unix_domain_socket_sendfd(srv->child[pick_worker_index].unix_domain_socket_fd[0], connection_fd)<0){ log_to_backend(srv,MINIHTTPD_LOG_LEVEL_ERROR,"failed to send the connection file descriptor to worker!"); close(connection_fd); //just close it to tell the client,we can not handle it now. continue; } srv->child[pick_worker_index].sent_connection_number++; //close the file descriptor as it is already marked in flight close(connection_fd); } }