百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

nginx源码剖析—nginx进程模型(nginx进阶)

ztj100 2025-03-26 19:20 17 浏览 0 评论

1.nginx进程模型

nginx采用的是多进程模型,典型的master-worker方式,采用一个master process(监控进程,也叫做主进程)和多个woker process(工作进程)的设计方式,此外,还有1个可选的chache manager和 1 个可选的cache loader进程。

这样设计的好处:

(1)利用多核系统的并发处理能力;

(2)负载均衡;

(3)管理进程会负责监控工作进程的状态,并负责管理其行为。


2. master-worker 启动流程

启动nginx的主进程将充当master进程,而由主进程fork()出来的子进程则充当工作进程。nginx也可以单进程模型执行,在这种进程模型下,主进程就是工作进程,没有监控进程。

Nginx的核心进程模型框图如下:

3.master进程是如何工作的


监控进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不需要处理网络事件,不负责业务的执行,只会通过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。

其工作流程如下图,它首先fork出worker进程,然后进入for循环,在每一帧循环中调用sigsupend挂起等待信号,如果收到信号,就处理信号,改变响应的标志位,然后根据标志位做出相应的操作。


master进程的标志位有如下7个:

   1: sig_atomic_t ngx_reap;
   2: sig_atomic_t ngx_terminate;
   3: sig_atomic_t ngx_quit;
   4: sig_atomic_t ngx_reconfigure;
   5: sig_atomic_t ngx_reopen;
   6: sig_atomic_t ngx_change_binary;
   7: sig_atomic_t ngx_noaccept;


每个标志与信号位的及作用的对应关系如下表:

这样,master进程的运行情况如下图:

【文章福利】:小编整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!正在跳转(需要自取)


下面看一下核心代码:

3.1 master进程住循环函数 ngx_master_process_cycle

   1: void
   2: ngx_master_process_cycle(ngx_cycle_t *cycle)
   3: {
   4:     char              *title;
   5:     u_char            *p;
   6:     size_t             size;
   7:     ngx_int_t          i;
   8:     ngx_uint_t         n, sigio;
   9:     sigset_t           set;
  10:     struct itimerval   itv;
  11:     ngx_uint_t         live;
  12:     ngx_msec_t         delay;
  13:     ngx_listening_t   *ls;
  14:     ngx_core_conf_t   *ccf;
  15:  
  16:     //设置屏蔽信号
  17:     sigemptyset(&set);
  18:     sigaddset(&set, SIGCHLD);
  19:     sigaddset(&set, SIGALRM);
  20:     sigaddset(&set, SIGIO);
  21:     sigaddset(&set, SIGINT);
  22:     sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
  23:     sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
  24:     sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
  25:     sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
  26:     sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
  27:     sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
  28:  
  29:     if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
  30:         ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  31:                       "sigprocmask() failed");
  32:     }
  33:  
  34:     sigemptyset(&set);
  35:  
  36:  
  37:     size = sizeof(master_process);
  38:  
  39:     for (i = 0; i < ngx_argc i 40: size 1 41: 42: 43: title='ngx_pnalloc(cycle-'>pool, size);
  44:  
  45:     p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
  46:     for (i = 0; i < ngx_argc i 47: p 48: p='ngx_cpystrn(p,' u_char ngx_argvi size 49: 50: 51: ngx_setproctitletitle 52: 53: 54: ccf='(ngx_core_conf_t' ngx_get_confcycle->conf_ctx, ngx_core_module);
  55:  
  56:     //其中包含了fork产生子进程的内容
  57:     ngx_start_worker_processes(cycle, ccf->worker_processes,
  58:                                NGX_PROCESS_RESPAWN);
  59:     //Cache管理进程与cache加载进程的主流程
  60:     ngx_start_cache_manager_processes(cycle, 0);
  61:  
  62:     ngx_new_binary = 0;
  63:     delay = 0;
  64:     sigio = 0;
  65:     live = 1;
  66:  
  67:     for ( ;; ) {//循环
  68:         if (delay) {
  69:             if (ngx_sigalrm) {
  70:                 sigio = 0;
  71:                 delay *= 2;
  72:                 ngx_sigalrm = 0;
  73:             }
  74:  
  75:             ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
  76:                            "termination cycle: %d", delay);
  77:  
  78:             itv.it_interval.tv_sec = 0;
  79:             itv.it_interval.tv_usec = 0;
  80:             itv.it_value.tv_sec = delay / 1000;
  81:             itv.it_value.tv_usec = (delay % 1000 ) * 1000;
  82:  
  83:             if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
  84:                 ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  85:                               "setitimer() failed");
  86:             }
  87:         }
  88:  
  89:         ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
  90:  
  91:         sigsuspend(&set);//master进程休眠,等待接受信号被激活
  92:  
  93:         ngx_time_update();
  94:  
  95:         ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
  96:                        "wake up, sigio %i", sigio);
  97:  
  98:         //标志位为1表示需要监控所有子进程
  99:         if (ngx_reap) {
 100:             ngx_reap = 0;
 101:             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
 102:  
 103:             live = ngx_reap_children(cycle);//管理子进程
 104:         }
 105:  
 106:         //当live标志位为0(表示所有子进程已经退出)、ngx_terminate标志位为1或者ngx_quit标志位为1表示要退出master进程
 107:         if (!live && (ngx_terminate || ngx_quit)) {
 108:             ngx_master_process_exit(cycle);//退出master进程
 109:         }
 110:  
 111:         //ngx_terminate标志位为1,强制关闭服务,发送TERM信号到所有子进程
 112:         if (ngx_terminate) {
 113:             if (delay == 0) {
 114:                 delay = 50;
 115:             }
 116:  
 117:             if (sigio) {
 118:                 sigio--;
 119:                 continue;
 120:             }
 121:  
 122:             sigio = ccf->worker_processes + 2 /* cache processes */;
 123:  
 124:             if (delay > 1000) {
 125:                 ngx_signal_worker_processes(cycle, SIGKILL);
 126:             } else {
 127:                 ngx_signal_worker_processes(cycle,
 128:                                        ngx_signal_value(NGX_TERMINATE_SIGNAL));
 129:             }
 130:  
 131:             continue;
 132:         }
 133:  
 134:         //ngx_quit标志位为1,优雅的关闭服务
 135:         if (ngx_quit) {
 136:             ngx_signal_worker_processes(cycle,
 137:                                         ngx_signal_value(NGX_SHUTDOWN_SIGNAL));//向所有子进程发送quit信号
 138:  
 139:             ls = cycle->listening.elts;
 140:             for (n = 0; n < cycle->listening.nelts; n++) {//关闭监听端口
 141:                 if (ngx_close_socket(ls[n].fd) == -1) {
 142:                     ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
 143:                                   ngx_close_socket_n " %V failed",
 144:                                   &ls[n].addr_text);
 145:                 }
 146:             }
 147:             cycle->listening.nelts = 0;
 148:  
 149:             continue;
 150:         }
 151:  
 152:         //ngx_reconfigure标志位为1,重新读取配置文件
 153:         //nginx不会让原来的worker子进程再重新读取配置文件,其策略是重新初始化ngx_cycle_t结构体,用它来读取新的额配置文件
 154:         //再创建新的额worker子进程,销毁旧的worker子进程
 155:         if (ngx_reconfigure) {
 156:             ngx_reconfigure = 0;
 157:  
 158:             //ngx_new_binary标志位为1,平滑升级Nginx
 159:             if (ngx_new_binary) {
 160:                 ngx_start_worker_processes(cycle, ccf->worker_processes,
 161:                                            NGX_PROCESS_RESPAWN);
 162:                 ngx_start_cache_manager_processes(cycle, 0);
 163:                 ngx_noaccepting = 0;
 164:  
 165:                 continue;
 166:             }
 167:  
 168:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
 169:  
 170:             //初始化ngx_cycle_t结构体
 171:             cycle = ngx_init_cycle(cycle);
 172:             if (cycle == NULL) {
 173:                 cycle = (ngx_cycle_t *) ngx_cycle;
 174:                 continue;
 175:             }
 176:  
 177:             ngx_cycle = cycle;
 178:             ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
 179:                                                    ngx_core_module);
 180:             //创建新的worker子进程
 181:             ngx_start_worker_processes(cycle, ccf->worker_processes,
 182:                                        NGX_PROCESS_JUST_RESPAWN);
 183:             ngx_start_cache_manager_processes(cycle, 1);
 184:  
 185:             /* allow new processes to start */
 186:             ngx_msleep(100);
 187:  
 188:             live = 1;
 189:             //向所有子进程发送QUIT信号
 190:             ngx_signal_worker_processes(cycle,
 191:                                         ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
 192:         }
 193:         //ngx_restart标志位在ngx_noaccepting(表示正在停止接受新的连接)为1的时候被设置为1.
 194:         //重启子进程
 195:         if (ngx_restart) {
 196:             ngx_restart = 0;
 197:             ngx_start_worker_processes(cycle, ccf->worker_processes,
 198:                                        NGX_PROCESS_RESPAWN);
 199:             ngx_start_cache_manager_processes(cycle, 0);
 200:             live = 1;
 201:         }
 202:  
 203:         //ngx_reopen标志位为1,重新打开所有文件
 204:         if (ngx_reopen) {
 205:             ngx_reopen = 0;
 206:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
 207:             ngx_reopen_files(cycle, ccf->user);
 208:             ngx_signal_worker_processes(cycle,
 209:                                         ngx_signal_value(NGX_REOPEN_SIGNAL));
 210:         }
 211:  
 212:         //平滑升级Nginx
 213:         if (ngx_change_binary) {
 214:             ngx_change_binary = 0;
 215:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
 216:             ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
 217:         }
 218:  
 219:         //ngx_noaccept为1,表示所有子进程不再处理新的连接
 220:         if (ngx_noaccept) {
 221:             ngx_noaccept = 0;
 222:             ngx_noaccepting = 1;
 223:             ngx_signal_worker_processes(cycle,
 224:                                         ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
 225:         }
 226:     }
 227: }


3.2 master 产生master进程函数
ngx_start_worker_processes

   1: static void
   2: ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
   3: {
   4:     ngx_int_t      i;
   5:     ngx_channel_t  ch;
   6:  
   7:     ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");
   8:  
   9:     ch.command = NGX_CMD_OPEN_CHANNEL;
  10:  
  11:     //循环创建n个worker子进程
  12:     for (i = 0; i < n; i++) {
  13:         //完成fok新进程的具体工作
  14:         ngx_spawn_process(cycle, ngx_worker_process_cycle,
  15:                           (void *) (intptr_t) i, "worker process", type);
  16:  
  17:         //全局数组ngx_processes就是用来存储每个子进程的相关信息,如:pid,channel,进程做具体事情的接口指针等等,这些信息就是用结构体ngx_process_t来描述的。
  18:         ch.pid = ngx_processes[ngx_process_slot].pid;
  19:         ch.slot = ngx_process_slot;
  20:         ch.fd = ngx_processes[ngx_process_slot].channel[0];
  21:  
  22:         /*在ngx_spawn_process创建好一个worker进程返回后,master进程就将worker进程的pid、worker进程在ngx_processes数组中的位置及channel[0]传递给前面已经创建好的worker进程,然后继续循环开始创建下一个worker进程。刚提到一个channel[0],这里简单说明一下:channel就是一个能够存储2个整型元素的数组而已,这个channel数组就是用于socketpair函数创建一个进程间通道之用的。master和worker进程以及worker进程之间都可以通过这样的一个通道进行通信,这个通道就是在ngx_spawn_process函数中fork之前调用socketpair创建的。*/
  23:         ngx_pass_open_channel(cycle, &ch);
  24:     }

25: }

3.3 真正的fork函数 ngx_spawn_process

   1: //参数解释:
   2: //cycle:nginx框架所围绕的核心结构体
   3: //proc:子进程中将要执行的工作循环
   4: //data:参数
   5: //name:子进程名字
   6: ngx_pid_t
   7: ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
   8:     char *name, ngx_int_t respawn)
   9: {
  10:     u_long     on;
  11:     ngx_pid_t  pid;
  12:     ngx_int_t  s;
  13:  
  14:     if (respawn >= 0) {
  15:         s = respawn;
  16:  
  17:     } else {
  18:         for (s = 0; s < ngx_last_process s 19: if ngx_processess.pid='= -1)' 20: break 21: 22: 23: 24: if s='= NGX_MAX_PROCESSES)' 25: ngx_log_errorngx_log_alert cycle->log, 0,
  26:                           "no more than %d processes can be spawned",
  27:                           NGX_MAX_PROCESSES);
  28:             return NGX_INVALID_PID;
  29:         }
  30:     }
  31:  
  32:  
  33:     if (respawn != NGX_PROCESS_DETACHED) {
  34:  
  35:         /* Solaris 9 still has no AF_LOCAL */
  36:         //创建父子进程间通信的套接字对(基于TCP)
  37:         if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
  38:         {
  39:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  40:                           "socketpair() failed while spawning \"%s\"", name);
  41:             return NGX_INVALID_PID;
  42:         }
  43:  
  44:         ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
  45:                        "channel %d:%d",
  46:                        ngx_processes[s].channel[0],
  47:                        ngx_processes[s].channel[1]);
  48:  
  49:         //设置为非阻塞模式
  50:         if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
  51:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  52:                           ngx_nonblocking_n " failed while spawning \"%s\"",
  53:                           name);
  54:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  55:             return NGX_INVALID_PID;
  56:         }
  57:  
  58:         if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
  59:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  60:                           ngx_nonblocking_n " failed while spawning \"%s\"",
  61:                           name);
  62:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  63:             return NGX_INVALID_PID;
  64:         }
  65:  
  66:         on = 1;
  67:         if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
  68:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  69:                           "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
  70:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  71:             return NGX_INVALID_PID;
  72:         }
  73:  
  74:         if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
  75:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  76:                           "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
  77:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  78:             return NGX_INVALID_PID;
  79:         }
  80:  
  81:         if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
  82:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  83:                           "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
  84:                            name);
  85:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  86:             return NGX_INVALID_PID;
  87:         }
  88:  
  89:         if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
  90:             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  91:                           "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
  92:                            name);
  93:             ngx_close_channel(ngx_processes[s].channel, cycle->log);
  94:             return NGX_INVALID_PID;
  95:         }
  96:  
  97:         ngx_channel = ngx_processes[s].channel[1];
  98:  
  99:     } else {
 100:         ngx_processes[s].channel[0] = -1;
 101:         ngx_processes[s].channel[1] = -1;
 102:     }
 103:  
 104:     ngx_process_slot = s;
 105:  
 106:     //创建子进程
 107:     pid = fork();
 108:  
 109:     switch (pid) {
 110:  
 111:     case -1:
 112:         ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
 113:                       "fork() failed while spawning \"%s\"", name);
 114:         ngx_close_channel(ngx_processes[s].channel, cycle->log);
 115:         return NGX_INVALID_PID;
 116:  
 117:     case 0:
 118:         ngx_pid = ngx_getpid();
 119:         proc(cycle, data);
 120:         break;
 121:  
 122:     default:
 123:         break;
 124:     }
 125:  
 126:     ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);
 127:  
 128:     ngx_processes[s].pid = pid;
 129:     ngx_processes[s].exited = 0;
 130:  
 131:     if (respawn >= 0) {
 132:         return pid;
 133:     }
 134:  
 135:     ngx_processes[s].proc = proc;
 136:     ngx_processes[s].data = data;
 137:     ngx_processes[s].name = name;
 138:     ngx_processes[s].exiting = 0;
 139:  
 140:     switch (respawn) {
 141:  
 142:     case NGX_PROCESS_NORESPAWN:
 143:         ngx_processes[s].respawn = 0;
 144:         ngx_processes[s].just_spawn = 0;
 145:         ngx_processes[s].detached = 0;
 146:         break;
 147:  
 148:     case NGX_PROCESS_JUST_SPAWN:
 149:         ngx_processes[s].respawn = 0;
 150:         ngx_processes[s].just_spawn = 1;
 151:         ngx_processes[s].detached = 0;
 152:         break;
 153:  
 154:     case NGX_PROCESS_RESPAWN:
 155:         ngx_processes[s].respawn = 1;
 156:         ngx_processes[s].just_spawn = 0;
 157:         ngx_processes[s].detached = 0;
 158:         break;
 159:  
 160:     case NGX_PROCESS_JUST_RESPAWN:
 161:         ngx_processes[s].respawn = 1;
 162:         ngx_processes[s].just_spawn = 1;
 163:         ngx_processes[s].detached = 0;
 164:         break;
 165:  
 166:     case NGX_PROCESS_DETACHED:
 167:         ngx_processes[s].respawn = 0;
 168:         ngx_processes[s].just_spawn = 0;
 169:         ngx_processes[s].detached = 1;
 170:         break;
 171:     }
 172:  
 173:     if (s == ngx_last_process) {
 174:         ngx_last_process++;
 175:     }
 176:  
 177:     return pid;
 178: }


4.worker 进程是如何工作的

worker进程的主要任务是完成具体的任务逻辑。其主要关注点是与客户端或后端真实服务器(此时nginx作为中间代理)之间的数据可读/可写等I/O交互事件,所以工作进程的阻塞点是在像select()、epoll_wait()等这样的I/O多路复用函数调用处,以等待发生数据可读/写事件。当然也可能被新收到的进程信号中断。

master进程是信号来控制worker进程的。当收到信号时,信号处理函数ngx_signal_handler()就会执行。worker进程感兴趣的信号有4个,也是通过4个变量来控制,如下表:

其工作流程如下图所示:

其核心代码如下:

   1: static void
   2: ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
   3: {
   4:     ngx_int_t worker = (intptr_t) data;
   5:  
   6:     ngx_uint_t         i;
   7:     ngx_connection_t  *c;
   8:  
   9:     ngx_process = NGX_PROCESS_WORKER;
  10:  
  11:     //子进程初始化
  12:     ngx_worker_process_init(cycle, worker);
  13:  
  14:     ngx_setproctitle("worker process");
  15:  
  16: //这里有一段多线程条件下的代码。由于nginx并不支持多线程,因此删除掉了
  17:  
  18:     //循环
  19:     for ( ;; ) {
  20:         
  21:         //ngx_exiting标志位为1,进程退出
  22:         if (ngx_exiting) {
  23:             c = cycle->connections;
  24:             for (i = 0; i < cycle->connection_n; i++) {
  25:                 if (c[i].fd != -1 && c[i].idle) {
  26:                     c[i].close = 1;
  27:                     c[i].read->handler(c[i].read);
  28:                 }
  29:             }
  30:  
  31:             if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
  32:             {
  33:                 ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
  34:                 ngx_worker_process_exit(cycle);
  35:             }
  36:         }
  37:  
  38:         ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
  39:  
  40:         ngx_process_events_and_timers(cycle);//处理事件的方法
  41:  
  42:         //强制结束进程
  43:         if (ngx_terminate) {
  44:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
  45:             ngx_worker_process_exit(cycle);
  46:         }
  47:  
  48:         //优雅地退出进程
  49:         if (ngx_quit) {
  50:             ngx_quit = 0;
  51:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
  52:                           "gracefully shutting down");
  53:             ngx_setproctitle("worker process is shutting down");
  54:  
  55:             if (!ngx_exiting) {
  56:                 ngx_close_listening_sockets(cycle);
  57:                 //设置ngx_exiting 标志位
  58:                 ngx_exiting = 1;
  59:             }
  60:         }
  61:  
  62:         //重新打开所有文件
  63:         if (ngx_reopen) {
  64:             ngx_reopen = 0;
  65:             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
  66:             ngx_reopen_files(cycle, -1);
  67:         }
  68:     }
  69: }

————————————————

版权声明:本文为CSDN博主「chen19870707」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:
https://blog.csdn.net/chen19870707/article/details/41245067

相关推荐

使用 Pinia ORM 管理 Vue 中的状态

转载说明:原创不易,未经授权,谢绝任何形式的转载状态管理是构建任何Web应用程序的重要组成部分。虽然Vue提供了管理简单状态的技术,但随着应用程序复杂性的增加,处理状态可能变得更具挑战性。这就是为什么...

Vue3开发企业级音乐Web App 明星讲师带你学习大厂高质量代码

Vue3开发企业级音乐WebApp明星讲师带你学习大厂高质量代码下栽课》jzit.top/392/...

一篇文章说清 webpack、vite、vue-cli、create-vue 的区别

webpack、vite、vue-cli、create-vue这些都是什么?看着有点晕,不要怕,我们一起来分辨一下。...

超赞 vue2/3 可视化打印设计VuePluginPrint

今天来给大家推荐一款非常不错的Vue可拖拽打印设计器Hiprint。引入使用//main.js中引入安装import{hiPrintPlugin}from'vue-plugin-...

搭建Trae+Vue3的AI开发环境(vue3 ts开发)

从2024年2025年,不断的有各种AI工具会在自媒体中火起来,号称各种效率王炸,而在AI是否会替代打工人的话题中,程序员又首当其冲。...

如何在现有的Vue项目中嵌入 Blazor项目?

...

Vue中mixin怎么理解?(vue的mixins有什么用)

作者:qdmryt转发链接:https://mp.weixin.qq.com/s/JHF3oIGSTnRegpvE6GSZhg前言...

Vue脚手架安装,初始化项目,打包并用Tomcat和Nginx部署

1.创建Vue脚手架#1.在本地文件目录创建my-first-vue文件夹,安装vue-cli脚手架:npminstall-gvue-cli安装过程如下图所示:创建my-first-vue...

新手如何搭建个人网站(小白如何搭建个人网站)

ElementUl是饿了么前端团队推出的桌面端UI框架,具有是简洁、直观、强悍和低学习成本等优势,非常适合初学者使用。因此,本次项目使用ElementUI框架来完成个人博客的主体开发,欢迎大家讨论...

零基础入门vue开发(vue快速入门与实战开发)

上面一节我们已经成功的安装了nodejs,并且配置了npm的全局环境变量,那么这一节我们就来正式的安装vue-cli,然后在webstorm开发者工具里运行我们的vue项目。这一节有两种创建vue项目...

.net core集成vue(.net core集成vue3)

react、angular、vue你更熟悉哪个?下边这个是vue的。要求需要你的计算机安装有o.netcore2.0以上版本onode、webpack、vue-cli、vue(npm...

使用 Vue 脚手架,为什么要学 webpack?(一)

先问大家一个很简单的问题:vueinitwebpackprjectName与vuecreateprojectName有什么区别呢?它们是Vue-cli2和Vue-cli3创建...

vue 构建和部署(vue项目部署服务器)

普通的搭建方式(安装指令)安装Node.js检查node是否已安装,终端输入node-v会使用命令行(安装)npminstallvue-cli-首先安装vue-clivueinitwe...

Vue.js 环境配置(vue的环境搭建)

说明:node.js和vue.js的关系:Node.js是一个基于ChromeV8引擎的JavaScript运行时环境;类比:Java的jvm(虚拟机)...

vue项目完整搭建步骤(vuecli项目搭建)

简介为了让一些不太清楚搭建前端项目的小白,更快上手。今天我将一步一步带领你们进行前端项目的搭建。前端开发中需要用到框架,那vue作为三大框架主流之一,在工作中很常用。所以就以vue为例。...

取消回复欢迎 发表评论: