网站建设维护方向平台建设包括哪些方面

当前位置: 首页 > news >正文

网站建设维护方向,平台建设包括哪些方面,网站查外链,wordpress中文版插件文章目录1、项目介绍2、项目流程2.1、环境变量搬家2.2、设置进程title2.3、信号初始化2.4、开始监听端口2.5、创建守护进程2.6、创建子进程1、项目介绍 1、按照包头包体的格式收发数据包#xff0c;解决粘包的问题 2、非常完整的多线程高并发服务器 3、根据收到数据包执行包体的格式收发数据包解决粘包的问题 2、非常完整的多线程高并发服务器 3、根据收到数据包执行不同的业务逻辑函数 用到的技术 epoll高并发通讯技术用的是水平触发【LT】水平触发模式 通过线程池技术处理业务逻辑 多线程、之间同步技术使用互斥量、和条件变量 一个master管理进程多个worker工作进程 信号、守护进程 2、项目流程 2.1、环境变量搬家 1、统计argv总大小以及环境变量总共占用多少内存然后创建出来一块内存将原先的环境变量拷贝到我们创建的内存中为后续设置进程名做铺垫。 2、代码如下和我们描述过程一直没有多余操作 //统计argv所占的内存g_argvneedmem 0;for(int i 0; i argc; i) //argv ./nginx -a -b -c asdfas{g_argvneedmem strlen(argv[i]) 1; //1是给\0留空间。} //统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记for(int i 0; environ[i]; i) {g_envneedmem strlen(environ[i]) 1; //1是因为末尾有\0,是占实际内存位置的要算进来} //end forg_os_argc argc; //保存参数个数g_os_argv (char **) argv; //保存参数指针//设置可执行程序标题相关函数分配内存并且把环境变量拷贝到新内存中来//这里无需判断penvmen NULL,有些编译器new会返回NULL有些会报异常但不管怎样如果在重要的地方new失败了你无法收场让程序失控崩溃助你发现问题为好 gp_envmem new char[g_envneedmem]; memset(gp_envmem,0,g_envneedmem); //内存要清空防止出现问题char *ptmp gp_envmem;//把原来的内存内容搬到新地方来for (int i 0; environ[i]; i) {size_t size strlen(environ[i])1 ; //不要拉下1否则内存全乱套了因为strlen是不包括字符串末尾的\0的strcpy(ptmp,environ[i]); //把原环境变量内容拷贝到新地方【新内存】environ[i] ptmp; //然后还要让新环境变量指向这段新内存ptmp size;}2.2、设置进程title 将进程名字传进来计算名字长度要是大于argv参数总长度和环境变量总长度之和就不然设置反之可以然后将argv[1]设置为NULL以免影响后续然后将title拷贝进去将名字之后的内存空间都置为空 void ngx_setproctitle(const char *title) {//我们假设所有的命令 行参数我们都不需要用到了可以被随意覆盖了//注意我们的标题长度不会长到原始标题和原始环境变量都装不下否则怕出问题不处理//(1)计算新标题长度size_t ititlelen strlen(title); //(2)计算总的原始的argv那块内存的总长度【包括各种参数】 size_t esy g_argvneedmem g_envneedmem; //argv和environ内存总和if( esy ititlelen){//你标题多长啊我argv和environ总和都存不下注意字符串末尾多了个 \0所以这块判断是 【也就是都算存不下】return;}//空间够保存标题的够长存得下继续走下来 //(3)设置后续的命令行参数为空表示只有argv[]中只有一个元素了这是好习惯防止后续argv被滥用因为很多判断是用argv[] NULL来做结束标记判断的;g_os_argv[1] NULL; //(4)把标题弄进来注意原来的命令行参数都会被覆盖掉不要再使用这些命令行参数,而且g_os_argv[1]已经被设置为NULL了char *ptmp g_os_argv[0]; //让ptmp指向g_os_argv所指向的内存strcpy(ptmp,title);ptmp ititlelen; //跳过标题//(5)把剩余的原argv以及environ所占的内存全部清0否则会出现在ps的cmd列可能还会残余一些没有被覆盖的内容size_t cha esy - ititlelen; //内存总和减去标题字符串长度(不含字符串末尾的\0)剩余的大小就是要memset的memset(ptmp,0,cha); }2.3、信号初始化 需要设置信号处理函数的信号数组定义 typedef struct {int signo; //信号对应的数字编号 每个信号都有对应的#defineconst char *signame; //信号对应的中文名字 比如SIGHUP //信号处理函数,这个函数由我们自己来提供但是它的参数和返回值是固定的void (*handler)(int signo, siginfo_t *siginfo, void *ucontext); //函数指针, siginfo_t:系统定义的结构 } ngx_signal_t;static ngx_signal_t signals[] {// signo signame handler{ SIGHUP, SIGHUP, ngx_signal_handler }, //终端断开信号对于守护进程常用于reload重载配置文件通知–标识1{ SIGINT, SIGINT, ngx_signal_handler }, //标识2 { SIGTERM, SIGTERM, ngx_signal_handler }, //标识15{ SIGCHLD, SIGCHLD, ngx_signal_handler }, //子进程退出时父进程会收到这个信号–标识17{ SIGQUIT, SIGQUIT, ngx_signal_handler }, //标识3{ SIGIO, SIGIO, ngx_signal_handler }, //指示一个异步I/O事件【通用异步I/O信号】{ SIGSYS, SIGSYS, SIG_IGN, NULL }, //我们想忽略这个信号SIGSYS表示收到了一个无效系统调用如果我们不忽略进程会被操作系统杀死–标识31//所以我们把handler设置为NULL代表 我要求忽略这个信号请求操作系统不要执行缺省的该信号处理动作杀掉我//…日后根据需要再继续增加{ 0, NULL, NULL } //信号对应的数字至少是1所以可以用0作为一个特殊标记 };为以下的信号通过sigaction函数设置信号处理函数 //初始化信号的函数用于注册信号处理程序 //返回值0成功 -1失败 int ngx_init_signals() {ngx_signal_t *sig; //指向自定义结构数组的指针 struct sigaction sa; //sigaction系统定义的跟信号有关的一个结构我们后续调用系统的sigaction()函数时要用到这个同名的结构for (sig signals; sig-signo ! 0; sig) //将signo 0作为一个标记因为信号的编号都不为0{ //我们注意现在要把一堆信息往 变量sa对应的结构里弄 ……memset(sa,0,sizeof(struct sigaction));if (sig-handler) //如果信号处理函数不为空这当然表示我要定义自己的信号处理函数{sa.sa_sigaction sig-handler; //sa_sigaction指定信号处理程序(函数)注意sa_sigaction也是函数指针是这个系统定义的结构sigaction中的一个成员函数指针成员sa.sa_flags SA_SIGINFO; //sa_flagsint型指定信号的一些选项设置了该标记(SA_SIGINFO)就表示信号附带的参数可以被传递到信号处理函数中//说白了就是你要想让sa.sa_sigaction指定的信号处理程序(函数)生效你就把sa_flags设定为SA_SIGINFO}else{sa.sa_handler SIG_IGN; //sa_handler:这个标记SIG_IGN给到sa_handler成员表示忽略信号的处理程序否则操作系统的缺省信号处理程序很可能把这个进程杀掉//其实sa_handler和sa_sigaction都是一个函数指针用来表示信号处理程序。只不过这两个函数指针他们参数不一样 sa_sigaction带的参数多信息量大//而sa_handler带的参数少信息量少如果你想用sa_sigaction那么你就需要把sa_flags设置为SA_SIGINFO } //end ifsigemptyset(sa.sa_mask); //比如咱们处理某个信号比如SIGUSR1信号时不希望收到SIGUSR2信号那咱们就可以用诸如sigaddset(sa.sa_mask,SIGUSR2);这样的语句针对信号为SIGUSR1时做处理这个sigaddset//这里.sa_mask是个信号集描述信号的集合用于表示要阻塞的信号sigemptyset()把信号集中的所有信号清0本意就是不准备阻塞任何信号//设置信号处理动作(信号处理函数)说白了这里就是让这个信号来了后调用我的处理程序有个老的同类函数叫signal不过signal这个函数被认为是不可靠信号语义不建议使用大家统一用sigactionif (sigaction(sig-signo, sa, NULL) -1) //参数1要操作的信号//参数2主要就是那个信号处理函数以及执行信号处理函数时候要屏蔽的信号等等内容//参数3返回以往的对信号的处理方式【跟sigprocmask()函数边的第三个参数是的】跟参数2同一个类型我们这里不需要这个东西所以直接设置为NULL{ return -1; //有失败就直接返回} else{ }} //end forreturn 0; //成功
} 信号处理函数功能目前只要只是做了master进程和worker进程信号分类处理因为我们是进程程序当woker进程死亡时使用waitpid进行回收防止变成僵尸进程。 //信号处理函数 //siginfo这个系统定义的结构中包含了信号产生原因的有关信息 static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext) { //printf(来信号了\n); ngx_signal_t *sig; //自定义结构char *action; //一个字符串用于记录一个动作字符串以往日志文件中写for (sig signals; sig-signo ! 0; sig) //遍历信号数组 { //找到对应信号即可处理if (sig-signo signo) { break;}} //end foraction (char ); //目前还没有什么动作if(ngx_process NGX_PROCESS_MASTER) //master进程管理进程处理的信号一般会比较多 {//master进程的往这里走switch (signo){case SIGCHLD: //一般子进程退出会收到该信号ngx_reap 1; //标记子进程状态变化日后master主进程的for(;;)循环中可能会用到这个变量【比如重新产生一个子进程】break;//…..其他信号处理以后待增加default:break;} //end switch}else if(ngx_process NGX_PROCESS_WORKER) //worker进程具体干活的进程处理的信号相对比较少{//worker进程的往这里走//……以后再增加//….}else{//非master非worker进程先啥也不干//do nothing} //end if(ngx_process NGX_PROCESS_MASTER)//这里记录一些日志信息//siginfo这个if(siginfo siginfo-si_pid) //si_pid sending process ID【发送该信号的进程id】{printf(signal %d (%s) received from %P%s, signo, sig-signame, siginfo-si_pid, action); }else{printf(signal %d (%s) received %s,signo, sig-signame, action);//没有发送该信号的进程id所以不显示发送该信号的进程id}//子进程状态有变化通常是意外退出if (signo SIGCHLD) {ngx_process_get_status(); //获取子进程的结束状态} //end ifreturn; }通过waitpid函数回收woker进程回收设置WNOHANG为非阻塞调用。 返回值 进程id正常返回 0 表示子进程还没有结束 -1 errno EINTR 信号中断 errno ECHILD 没有子进程 //获取子进程的结束状态防止单独kill子进程时子进程变成僵尸进程 static void ngx_process_get_status(void) {pid_t pid;int status;int err;int one0; //抄自官方nginx应该是标记信号正常处理过一次//当你杀死一个子进程时父进程会收到这个SIGCHLD信号。for ( ;; ) {// 这个waitpid说白了获取子进程的终止状态这样子进程就不会成为僵尸进程了//第一次waitpid返回一个 0值表示成功//第二次再循环回来再次调用waitpid会返回一个0表示子进程还没结束然后这里有return来退出pid waitpid(-1, status, WNOHANG); //第一个参数为-1表示等待任何子进程//第二个参数保存子进程的状态信息(大家如果想详细了解可以百度一下)。//第三个参数提供额外选项WNOHANG表示不要阻塞让这个waitpid()立即返回 if(pid 0) //子进程没结束会立即返回这个数字但这里应该不是这个数字【因为一般是子进程退出时会执行到这个函数】{return;} //end if(pid 0)//——————————-if(pid -1)//这表示这个waitpid调用有错误有错误也理解返回出去我们管不了这么多{//这里处理代码抄自官方nginx主要目的是打印一些日志。考虑到这些代码也许比较成熟所以就基本保持原样照抄吧err errno;if(err EINTR) //调用被某个信号中断{continue;}if(err ECHILD one) //没有子进程{return;}if (err ECHILD) //没有子进程{return;}return;} //end if(pid -1)//——————————-//走到这里表示 成功【返回进程id】 这里根据官方写法打印一些日志来记录子进程的退出one 1; //标记waitpid()返回了正常的返回值if(WTERMSIG(status)) //获取使子进程终止的信号编号{printf(pid %P exited on signal %d!,pid,WTERMSIG(status)); //获取使子进程终止的信号编号}else{printf(pid %P exited with code %d!,pid,WEXITSTATUS(status)); //WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位}} //end forreturn; }2.4、开始监听端口 监听这个端口是否有连接到来有连接上来并完成三次握手就会将这个连接放入以完成队列中等待accpet去连接这个用户并且将监听套接字设置为非阻塞。 // 监听套接字 void listen_socket() {connfd socket(AF_INET,SOCK_STREAM,0);if(connfd0){coutsocket failed:connfdendl;return;}// 设置端口服用解决TIME_WAIT状态int op 1;if(setsockopt(connfd,SOL_SOCKET,SO_REUSEADDR,(const void)op,sizeof(op))-1){coutsetsockopt failedendl;close(connfd);return;}// 设置非阻塞int nb 1;if(ioctl(connfd,FIONBIO,nb) -1){coutioctl failedendl;close(connfd);return;}// bind端口和ip地址struct sockaddr_in ser_addr;ser_addr.sin_family AF_INET;ser_addr.sin_port (in_port_t)(htons(PORT));ser_addr.sin_addr.s_addr htonl(INADDR_ANY);if(bind(connfd,(struct sockaddr)ser_addr,sizeof(ser_addr)) -1){coutbind failedendl;close(connfd);return;}// listen 监听套接字if(listen(connfd,511)-1) {coutlisten failedendl;close(connfd);return;} }设置套接字非阻塞方法 bool setnonblocking(int sockfd) { int nb1; //0清除1设置 if(ioctl(sockfd, FIONBIO, nb) -1) //FIONBIO设置/清除非阻塞I/O标记0清除1设置{return false;}return true;//如下也是一种写法跟上边这种写法其实是一样的但上边的写法更简单/ //fcntl:file control【文件控制】相关函数执行各种描述符控制操作//参数1所要设置的描述符这里是套接字【也是描述符的一种】int opts fcntl(sockfd, F_GETFL); //用F_GETFL先获取描述符的一些标志信息if(opts 0) {ngx_log_stderr(errno,CSocekt::setnonblocking()中fcntl(F_GETFL)失败.);return false;}opts | O_NONBLOCK; //把非阻塞标记加到原来的标记上标记这是个非阻塞套接字【如何关闭非阻塞呢opts ~O_NONBLOCK,然后再F_SETFL一下即可】if(fcntl(sockfd, F_SETFL, opts) 0) {ngx_log_stderr(errno,CSocekt::setnonblocking()中fcntl(F_SETFL)失败.);return false;}return true;*/ }2.5、创建守护进程 使程序脱离终端在后台运行 1、fork出一个子进程主进程退出所有流程子进程继续向下走 2、设置setsid脱离终端与父进程脱关系 3、设置umask文件权限 4、以读写方式打开/dev/null 5、将输入输出指向黑洞 6、关闭刚才打开的文件描述符 // 子进程返回0父进程返回1执行失败返回-1 static int ngx_daemon() {switch (fork()){case -1:coutngx_daemon failedendl;return -1; case 0:break;default:return 1;}// 只有fork出来的子进程才能走到这个流程if(setsid() -1){coutngx_daemon setsid failedendl;return -1;}umask(0);// 打开黑洞设备以读写方式打开int fd open(/dev/null,O_RDWR);if(fd -1) {coutngx_daemon open failedendl;return -1;}if(dup2(fd,STDIN_FILENO) -1){coutngx_daemon dup2 failedendl;return -1;}if(dup2(fd,STDOUT_FILENO)-1){coutngx_daemon dup2 failedendl;return -1;}if(fdSTDERR_FILENO){if(close(fd)-1){coutngx_daemon close failedendl;return -1;}}return 0; }2.6、创建子进程 master进程流程为了不让创建woker进程过程中被信号给打断为master进程设置信号集创建完子进程将信号集设置为空此时master进程进入死循环通过sigsuspend函数处理信号它会讲我们刚才恢复为空的信号集设置进去然后等待信号来临一旦收到信号将恢复之前我们设置的信号屏蔽字调用信号处理函数等待信号处理函数返回它才返回。 static u_char master_process[] master process; //描述创建worker子进程 void ngx_master_process_cycle() { sigset_t set; //信号集sigemptyset(set); //清空信号集//建议fork()子进程时学习这种写法防止信号的干扰sigaddset(set, SIGCHLD); //子进程状态改变sigaddset(set, SIGALRM); //定时器超时sigaddset(set, SIGIO); //异步I/Osigaddset(set, SIGINT); //终端中断符sigaddset(set, SIGHUP); //连接断开sigaddset(set, SIGUSR1); //用户定义信号sigaddset(set, SIGUSR2); //用户定义信号sigaddset(set, SIGWINCH); //终端窗口大小改变sigaddset(set, SIGTERM); //终止sigaddset(set, SIGQUIT); //终端退出符//………可以根据开发的实际需要往其中添加其他要屏蔽的信号……//设置此时无法接受的信号阻塞期间你发过来的上述信号多个会被合并为一个暂存着等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, set, NULL) -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{ printf(ngx_master_process_cycle()中sigprocmask()失败!);}//即便sigprocmask失败程序流程 也继续往下走//首先我设置主进程标题———beginsize_t size;int i;size sizeof(master_process); //注意我这里用的是sizeof所以字符串末尾的\0是被计算进来了的size g_argvneedmem; //argv参数长度加进来 if(size 1000) //长度小于这个我才设置标题{char title[1000] {0};strcpy(title,(const char *)master_process); //master processstrcat(title, ); //跟一个空格分开一些清晰 //master process for (i 0; i g_os_argc; i) //master process ./nginx{strcat(title,g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题printf(%s %P 【master进程】启动并开始运行……!,title,ngx_pid); //设置标题时顺便记录下来进程名进程id等信息到日志} //首先我设置主进程标题———endint workprocess 5;ngx_start_worker_processes(workprocess); //这里要创建worker子进程//创建子进程后父进程的执行流程会返回到这里子进程不会走进来 sigemptyset(set); //信号屏蔽字为空表示不屏蔽任何信号 for ( ;; ) {// usleep(100000);//ngx_log_error_core(0,0,haha–这是父进程pid为%P,ngx_pid);// sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行直到收到信号为止。// sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后进程将继续执行。该系统调用始终返回-1并将errno设置为EINTR。//sigsuspend是一个原子操作包含4个步骤//a)根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集所以不阻塞任何信号】//b)此时一旦收到信号便恢复原先的信号屏蔽【我们原来调用sigprocmask()的mask在上边设置的阻塞了多达10个信号从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后sigsuspend返回使程序流程继续往下走//printf(for进来了\n); //发现如果print不加\n无法及时显示到屏幕上是行缓存问题以往没注意可参考https://blog.csdn.net/qq_26093511/article/details/53255970sigsuspend(set); //阻塞在这里等待一个信号此时进程是挂起的不占用cpu时间只有收到信号才会被唤醒返回//此时master进程完全靠信号驱动干活 // printf(执行到sigsuspend()下边来了\n);//printf(master进程休息1秒\n); //ngx_log_stderr(0,haha–这是父进程pid为%P,ngx_pid); sleep(1); //休息1秒 //以后扩充…….}// end for(;;)return; } 通过for循环将所有子进程创建出来并且设置进程名字、信号集设置为空进入死循环后续子进程后续业务逻辑主要在子进程中完成master进程主要起管理作用。 //描述根据给定的参数创建指定数量的子进程因为以后可能要扩展功能增加参数所以单独写成一个函数 //threadnums:要创建的子进程数量 static void ngx_start_worker_processes(int threadnums) {int i;for (i 0; i threadnums; i) //master进程在走这个循环来创建若干个子进程{ngx_spawn_process(i,worker process);} //end forreturn; }//描述产生一个子进程 //inum进程编号【0开始】 //pprocname子进程名字worker process static int ngx_spawn_process(int inum,const char *pprocname) {pid_t pid;pid fork(); //fork()系统调用产生子进程switch (pid) //pid判断父子进程分支处理{ case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT,errno,ngx_spawn_process()fork()产生子进程num%d,procname\%s\失败!,inum,pprocname);return -1;case 0: //子进程分支ngx_parent ngx_pid; //因为是子进程了所有原来的pid变成了父pidngx_pid getpid(); //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum,pprocname); //我希望所有worker子进程在这个函数里不断循环着不出来也就是说子进程流程不往下边走;break;default: //这个应该是父进程分支直接break;流程往switch之后走 break;}//end switch//父进程分支会走到这里子进程流程不往下边走————————-//若有需要以后再扩展增加其他代码……return pid; }//描述worker子进程的功能函数每个woker子进程就在这里循环着了无限循环【处理网络事件和定时器事件以对外提供web服务】 // 子进程分叉才会走到这里 //inum进程编号【0开始】 static void ngx_worker_process_cycle(int inum,const char *pprocname) {//设置一下变量ngx_process NGX_PROCESS_WORKER; //设置进程的类型是worker进程//重新为子进程设置进程名不要与父进程重复——sigset_t set; //信号集sigemptyset(set); //清空信号集if (sigprocmask(SIG_SETMASK, set, NULL) -1) //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】现在不再屏蔽任何信号【接收任何信号】{coutngx_worker_process_cycle failedendl;}ngx_setproctitle(pprocname); //设置标题 printf(%s %P 【worker进程】启动并开始运行……!,pprocname,ngx_pid); //设置标题时顺便记录下来进程名进程id等信息到日志for(;;){} //end for(;;)return; }