1第三讲并发服务器Networkprogramming‘032目录服务器分类技术进程与线程多进程服务器多线程服务器Networkprogramming‘033服务器分类服务器分类按连接类型分类\面向连接的服务器(如tcp)\面向无连接的服务器(如udp)按处理方式分类\迭代服务器\并发服务器按状态保存分类\有状态服务器\无状态服务器Networkprogramming‘034迭代服务器vs.并发服务器绑定地址监听连接接收连接处理连接断开连接接收请求处理请求返回响应绑定地址监听连接接收连接创建子进程关闭连接套接字处理连接关闭连接套接字终止子进程关闭监听套接字服务器主进程服务器子进程TCP迭代服务器TCP并发服务器2Networkprogramming‘035“进程”基本概念进程定义了一个计算的基本单元,它是一个执行某一个特定程序的实体,它拥有独立的地址空间、执行堆栈、文件描述符等。进程间正常情况下,互不影响,一个进程的崩溃不会造成其他进程的崩溃。当进程间共享某一资源时,需注意两个问题:同步问题和通信问题。Networkprogramming‘036创建进程#includesys/types.h#includeunistd.hpid_tfork(void)返回:父进程中返回子进程的进程ID,子进程返回0,-1-出错(注意为什么?)fork后,父子进程共享数据空间、代码空间、堆栈、所有的文件描述字;如果父子进程同时对同一文件描述字操作,而又没有任何形式的同步,则会出现混乱的状况;非常重要的是:fork后,父子进程均需要将自己不使用的描述字关闭,有两方面的原因:(1)以免出现不同步的情况;(2)最后能正常关闭描述字Networkprogramming‘037创建进程(cont.)子进程继承父进程的大部分属性,如:\实际UID,GID和有效UID,GID;环境变量;UID、GID设置模式位;进程组号;控制终端;当前工作目录;根目录;文件创建掩码;文件长度限制;预定值(如优先级)等;但子进程也有与父进程不同的属性,如:\进程号;父进程号;子进程的用户时间和系统时间被初始化为0;子进程的超时时钟设置为0;子进程的信号处理函数指针置为空;子进程不继承父进程的记录锁等;Networkprogramming‘038创建进程(cont.)#includesys/types.h#includeunistd.hpid_tvfork(void);该系统调用基本上与fork相同,在BSD3.0中开始出现,主要为了解决fork昂贵的开销(自从有了“写时拷贝”技术后,vfork的优势就不存在了)。两者的基本区别在于当使用vfork()创建新进程时,父进程将被暂时阻塞,而子进程则可以借用父进程的地址空间。这个奇特状态将持续直到子进程要么退出,要么调用execve(),至此父进程才继续执行。因此,子进程需小心处理共享变量。3Networkprogramming‘039#includesys/types.h#includesys/types.h#includeunistd.h#includeunistd.h#includestdio.h#includestdio.hintmain(void){intmain(void){pid_tpid;pid_tpid;intstatus;intstatus;if((pid=vfork())==0){if((pid=vfork())==0){printf(childrunning.printf(childrunning.\\n);n);printf(childsleeping.printf(childsleeping.\\n);n);sleep(10);sleep(10);printf(childdead.printf(childdead.\\n);n);_exit(0);_exit(0);}elseif(pid0){}elseif(pid0){printf(parentrunning.printf(parentrunning.\\n);n);wait(&status);wait(&status);printf(parentexitprintf(parentexit\\n);n);}elseexit(0);}elseexit(0);exit(0);exit(0);}}Networkprogramming‘0310终止进程进程的终止存在两个可能:\父进程先于子进程终止(init进程)\子进程先于主进程终止对于后者,系统内核为子进程保留一定量的状态信息:进程ID、终止状态、CPU时间等;当父进程调用wait或waitpid函数是,获取这些信息;(什么叫“僵尸进程”?、“孤儿进程”)当子进程终止时,向父进程发送SIGCHLD信号;缺省情况下,父进程忽略该信号。Networkprogramming‘0311终止进程(续)#includestdlib.hvoidexit(intstatus);本函数终止调用进程。关闭所有子进程打开的描述符,释放占用的内存资源,并向父进程发送SIGCHLD信号。进程退出前,需要刷新I/O缓冲区,并执行由atexit或on_exit注册的函数。#includeunistd.hvoid_exit(intstatus);该函数除不执行上述该函数除不执行上述exitexit函数的第二条功能外,其他与函数的第二条功能外,其他与exitexit函数完全一样。函数完全一样。(一般情况下,(一般情况下,forkfork的子进程调用的子进程调用_exit_exit终终止自己,而不是调用止自己,而不是调用exitexit)。)。Networkprogramming‘0312获取子进程终止信息#includesys/types.h#includesys/wait.hpid_twait(int*stat_loc);返回:终止子进程的ID-成功;-1-出错;stat_loc存储子进程的返回值;该函数将挂起当前进程,直到有一个子进程终止或者被信号中断。当调用该系统调用时,如果有一个子进程已经终止,则该系统调用立即返回,并释放子进程所有资源。4Networkprogramming‘0313获取子进程终止信息(cont.)pid_twaitpid(pid_tpid,int*stat_loc,intoption);返回:终止子进程的ID-成功;-1-出错;stat_loc存储子进程的返回值;当pid=-1,option=0时,该函数等同于wait,否则由参数pid和option共同决定函数行为,其中pid参数意义如下:\-1:要求知道任何一个子进程的返回状态;\0:要求知道进程号为pid的子进程的状态;\=0:要求知道进程组号等于当前进程的进程组号的任一进程的状态。\-1:要求知道进程组号为pid的绝对值的任一子进程状态。Networkprogramming‘0314获取子进程终止信息(cont.)调用wait或waitpid函数时,可能会有以下几种情况:\阻塞(如果其所有子进程都还在运行);\获得子进程的终止状态并立即返回(如果一个子进程已终止,正等待父进程存取其终止状态);\出错立即返回(如果它没有任何子进程)Networkprogramming‘0315waitpid函数用法pid_tpid;if((pid=fork())0)/*parentprocess*/{intchild_status;waitpid(pid,&child_status,0);}elseif(pid==0){/*childprocess*/exit(0);}else{/*forkerror*/printf(“forkerror.\n”);exit(1);}Networkprogramming‘0316多进程并发服务器模板……intmain(void){intlistenfd,connfd;pid_tpid;intBACKLOG=5;if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1){perror(“Createsocketfailed.”);exit(1);}bind(listenfd,…);listen(listenfd,BACKLOG);while(1){if((connfd=accept(sockfd,NULL,NULL))==-1){perror(“Accepterror.”);exit(1);}5Networkprogramming‘0317多进程并发服务器模板(cont.)if((pid=fork())0){close(connfd);……conntinue;}elseif(pid==0){close(listenfd);……close(connfd);_exit(0);}else{perror(“Createchildprocessfailed.”);exit(1);}}}特别需要注意特别需要注意Networkprogramming‘0318一点说明从以上模板看出,产生新的子进程后,父进程要关闭连接套接字,而子进程要关闭监听套接字,主要原因是:\关闭不需要的套接字可节省系统资源,同时可避免父子进程共享这些套接字可能带来的不可预计的后果;\另一个更重要的原因,是为了正确地关闭连接。和文件描述符一样,每个套接字描述符都有一个“引用计数”。当fork函数返回后,listenfd和connfd的引用计数变为2,而系统只有在某描述符的“引用计数”为0时,才真正关闭该描述符。Networkprogramming‘0319多进程并发服务器状态图服务器客户connect()函数listenfd客户/服务器状态图(调用accept函数时)连接请求Networkprogramming‘0320多进程并发服务器状态图(cont.)服务器客户connect()函数listenfd客户/服务器状态图(调用accept函数后)connfd连接建立6Networkprogramming‘0321多进程并发服务器状态图(cont.)服务器(父进程)客户connect()函数listenfd客户/服务器状态图(调用fork函数后)connfd连接建立服务器(子进程)listenfdconnfdfork()函数Networkprogramming‘0322多进程并发服务器状态图(cont.)服务器(父进程)客户connect()函数listenfd客户/服务器状态图(父进程关闭连接套接字,子进程关闭监听套接字)连接建立服务器(子进程)connfdNetworkprogramming‘0323多进程并发服务器实例该实例包括服务器程序和客户程序,具体功能如下:\服务器等待客户连接,连接成功后显示客户地址,接着接收该客户的名字并显示,然后接收来自客户的信息(字符串),将该字符串反转,并将结果送回客户。要求服务器具有同时处理多个客户的能力。\客户首先与服务器连接,接着接收用户输入客户的名字,将该名字发送给服务器,然后接收用户输入的字符串,并发送给服务器,然后接收服务器返回的经处理后的字符串,并显示之。当用户输入Ctrl+D,终止连接并退出。Networkprogramming‘0324多进程并发服务器-服务器#includestdio.h……#definePORT1234#defineBACKLOG2#defineMAXDATASIZE1000voidprocess_cli(intconnectfd,sockaddr_inclient);intmain(void){intlistenfd,connectfd;pid_tpid;structsockaddr_intserver,client;intsin_size;/*CreateTCPSocket*/……7Networkprogramming‘0325