实现简易Web服务器(c语言)
任务:
(1)实现服务器与客户端间的通信。
(2)可以实现HTTP请求中的GET方法。
(3)提供静态网页浏览功能,如可浏览:HTML页面,无格式文本,常见图像格式等。
(4)提供可以传递参数的动态网页浏览功能。
(5)可以检查一些明显错误报告给客户端,如:403无权访问,404找不到所请求的文件,501不支持相应方法等。
(6)在服务器端可输出HTTP响应的相关信息。
服务器端可配置参数,如:主目录,首页文件名,HTTP端口号等项。
套接字接口
套接字接口是一组函数,它们和Unix I/O函数结合起来,用以创建网络网络应用。
客户端和服务器使用socket函数来创建一个套接字描述符。
服务器端通过bind函数告诉内核将addr中的服务器套接字地址和套接字描述符sockfd联系起来;通过listen函数告诉内核,描述符是被服务器而不是客户端使用;通过accept函数来等待来自客户端的连接请求。
HTTP(超文本传输协议)被用在Web客户端和服务器之间的交互。当客户端需要服务时,它就向服务器发送一个HTTP请求说明需要的东西,服务器收到后解析,然后进行回应。本次实验,实现了应用最广泛的GET方法,并通过解析,判断是需要动态内容,还是静态内容,还是错误处理。
doit函数:处理一个HTTP事务。
首先读和解析请求行,获得方法,如果不是所支持的GET方法,就发送给它一个错误消息,并返回到主程序,主程序随后关闭连接并等待下一个连接请求。否则,读并且调用read_requesthdrs忽略报头中的其他信息。然后将URI解析为一个文件名和一个可能为空的CGI参数字符串,并且设置一个标志,表明请求的是静态内容还是动态内容。如果所需的文件不存在,就发送一个错误信息到客户端并返回。
parse_uri函数:解析URI并转为所需的文件名。
初始时设主目录为当前目录,可执行文件的目录为./cgi-bin。如果请求的是衣一个静态内容,就清楚CGI参数字符串,然后转为文件的路径名。如果请求的是动态内容,就提取出所有的CGI参数,然后转为文件的路径名。
serve_static函数:处理静态内容。
首先打开filename文件,用mmap函数将文件映射到一个虚拟内存空间,然后关闭这个文件,rio_writen函数复制到客户端已连接的描述符。这样前段就可以显示出所需的内容。
serve_dynamic函数:处理动态内容。
首先向客户端发送成功相应的内容,然后派生一个子进程,子进程用来自请求URI的CGI参数初始化QUERY_STRING环境变量,重定向标准输出到已连接文件描述符,然后执行CGI程序。
signal_r(SIGCHLD, sigchild_handler)函数:处理僵尸进程函数。
fork创建出很多进程,这些进程执行完以后就exit(0)然后发个信号通知主进程,exit函数退出后,进程的有关资源(打开的文件描述符,栈,寄存器,有关信息等)还没有释放掉,如果进程不被回收的话就会占用存储器资源这样的进程就称为僵尸进程。所以解决办法就是主进程使用一个信号处理函数,等待僵尸进程回收。
1 /* 2 * @filename: webServer.c 3 * @author: Flyuz 4 * @date: 2018年6月25日 5 * @description: 主程序 6 */ 7 8 #include "functionLib.h" 9 10 void doit(int fd); 11 void serve_static(int fd, char *filename, int filesize); 12 int parse_uri(char *uri, char *filename, char *cgiargs); 13 void read_requesthdrs(rio_t *rp); 14 void clienterror(int fd, char *cause, char *errnum, char *shortmsg, 15 char *longmsg); 16 void get_filetype(char *filename, char *filetype); 17 void serve_dynamic(int fd, char *filename, char *cgiargs); 18 19 20 int main(int argc, char **argv) { 21 int listenfd, connfd; 22 socklen_t clientlen; 23 24 struct sockaddr_in clientaddr; 25 26 if (argc != 2) { 27 fprintf(stderr, "Usage: %s <port>\n", argv[0]); 28 exit(1); 29 } 30 31 printf("The web server has been started.....\n"); 32 33 listenfd = Open_listenfd(argv[1]); 34 /* 35 信号处理函数 36 用来处理僵尸进程 37 */ 38 signal_r(SIGCHLD, sigchild_handler); 39 40 while (1) { 41 clientlen = sizeof(clientaddr); 42 if((connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen)) < 0) 43 { 44 if(errno == EINTR) 45 continue; 46 else 47 printf("Accept error..."); 48 } 49 pid_t pid = Fork(); 50 if(pid == 0) 51 { 52 doit(connfd); 53 Close(connfd); 54 exit(0); 55 } 56 else 57 { 58 Close(connfd); 59 } 60 } 61 } 62 63 void doit(int fd) { 64 int is_static; 65 struct stat sbuf; 66 rio_t rio; 67 char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; 68 char filename[MAXLINE]; //设置根目录 69 char cgiargs[MAXLINE]; 70 71 //初始化 rio 结构 72 Rio_readinitb(&rio, fd); 73 //读取http请求行 74 Rio_readlineb(&rio, buf, MAXLINE); 75 //格式化存入 把该行拆分 76 sscanf(buf, "%s %s %s", method, uri, version); 77 78 //只能处理GET请求,如果不是GET请求的话返回错误 79 if (strcasecmp(method, "GET")) { 80 clienterror(fd, method, "501", "Not Implemented", 81 "Flyuz does not implement thid method"); 82 return; 83 } 84 85 //读取并忽略请求报头 86 read_requesthdrs(&rio); 87 88 // memset(filename,0,sizeof(filename)); 89 //解析 URI 90 is_static = parse_uri(uri, filename, cgiargs); 91 92 //文件不存在 93 if (stat(filename, &sbuf) < 0) { 94 clienterror(fd, filename, "404", "Not found", 95 "Flyuz couldn't find this file"); 96 return; 97 } 98 99 if (is_static) { //服务静态内容 100 if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { 101 clienterror(fd, filename, "403", "Forbidden", 102 "Flyuz couldn't read the file"); 103 return; 104 } 105 serve_static(fd, filename, sbuf.st_size); 106 } else { //服务动态内容 107 if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { 108 clienterror(fd, filename, "403", "Forbidden", 109 "Flyuz couldn't run the CGI program"); 110 return; 111 } 112 serve_dynamic(fd, filename, cgiargs); 113 } 114 } 115 116 /* 117 * 读取http 请求报头,无法使用请求报头的任何信息,读取之后忽略掉 118 */ 119 void read_requesthdrs(rio_t *rp) { 120 char buf[MAXLINE]; 121 122 Rio_readlineb(rp, buf, MAXLINE); 123 printf("%s", buf); 124 //空文本行终止请求报头,碰到 空行 就结束 空行后面是内容实体 125 while (strcmp(buf, "\r\n")) { 126 Rio_readlineb(rp, buf, MAXLINE); 127 printf("%s", buf); 128 } 129 return; 130 } 131 132 /* 133 * 解析URI 为 filename 和 CGI 参数 134 * 如果是动态内容返回0;静态内容返回 1 135 */ 136 int parse_uri(char *uri, char *filename, char *cgiargs) { 137 if (!strstr(uri, 138 "cgi-bin")) { //默认可执行文件都放在cgi-bin下,这里表示没有找到 139 strcpy(cgiargs, ""); 140 strcpy(filename, "."); 141 strcat(filename, uri); 142 /* 143 if(uri[strlen(uri)-1] == "/") //设置默认文件 144 strcat(filename, "index.html"); 145 */ 146 147 return 1; // static 148 } else { //动态内容 149 char *ptr = strchr(uri, '?'); 150 if (ptr) { //有参数 151 strcpy(cgiargs, ptr + 1); 152 *ptr = '\0'; 153 } else { //无参数 154 strcpy(cgiargs, ""); 155 } 156 157 strcpy(filename, "."); 158 strcat(filename, uri); 159 return 0; 160 } 161 } 162 163 /* 164 * 功能:发送一个HTTP响应,主体包含一个本地文件的内容 165 */ 166 void serve_static(int fd, char *filename, int filesize) { 167 int srcfd; 168 char *srcp, body[MAXBUF], filetype[MAXLINE]; 169 170 /* 发送 响应行 和 响应报头 */ 171 get_filetype(filename, filetype); 172 173 sprintf(body, "HTTP/1.0 200 OK\r\n"); 174 sprintf(body, "%sServer: Flyuz Web Server\r\n", body); 175 sprintf(body, "%sConnection:close\r\n", body); 176 sprintf(body, "%sContent-length: %d\r\n", body, filesize); 177 sprintf(body, "%sContent-type: %s\r\n\r\n", body, filetype); 178 Rio_writen(fd, body, strlen(body)); 179 printf("Response headers: \n%s", body); 180 181 /* 发送响应主体 即请求文件的内容 */ 182 /* 只读方式发开filename文件,得到描述符*/ 183 srcfd = Open(filename, O_RDONLY, 0); 184 /* 将srcfd 的前 filesize 个字节映射到一个从地址 srcp 开始的只读虚拟存储器区域 185 * 返回被映射区的指针 */ 186 srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); 187 /* 此后通过指针 srcp 操作,不需要这个描述符,所以关掉 */ 188 Close(srcfd); 189 190 Rio_writen(fd, srcp, filesize); 191 /* 释放映射的虚拟存储器区域 */ 192 Munmap(srcp, filesize); 193 } 194 195 /* 196 * 功能:从文件名得出文件的类型 197 */ 198 void get_filetype(char *filename, char *filetype) { 199 if (strstr(filename, ".html") || strstr(filename, ".php")) 200 strcpy(filetype, "text/html"); 201 else if (strstr(filename, ".gif")) 202 strcpy(filetype, "image/gif"); 203 else if (strstr(filename, ".png")) 204 strcpy(filetype, "image/png"); 205 else if (strstr(filename, ".ipg")) 206 strcpy(filetype, "image/jpeg"); 207 else 208 strcpy(filetype, "text/plain"); 209 } 210 211 /* 212 * 功能:运行客户端请求的CGI程序 213 */ 214 void serve_dynamic(int fd, char *filename, char *cgiargs) { 215 char buf[MAXLINE]; 216 char *emptylist[] = {NULL}; 217 218 /* 发送响应行 和 响应报头 */ 219 sprintf(buf, "HTTP/1.0 200 OK\r\n"); 220 Rio_writen(fd, buf, strlen(buf)); 221 sprintf(buf, "Server Flyuz Web Server\r\n"); 222 Rio_writen(fd, buf, strlen(buf)); 223 224 /* 剩下的内容由CGI程序负责发送 */ 225 if (Fork() == 0) { //子进程 226 setenv("QUERY_STRING", cgiargs, 1); 227 Dup2(fd, STDOUT_FILENO); 228 Execve(filename, emptylist, __environ); 229 } 230 Wait(NULL); 231 /* 232 if(strstr(filename, ".php")) { 233 sprintf(response, "HTTP/1.1 200 OK\r\n"); 234 sprintf(response, "%sServer: Pengge Web Server\r\n",response); 235 sprintf(response, "%sConnection: close\r\n",response); 236 sprintf(response, "%sContent-type: %s\r\n\r\n",response,filetype); 237 Write(connfd, response, strlen(response)); 238 printf("Response headers:\n"); 239 printf("%s\n",response); 240 php_cgi(filename, connfd,cgi_params); 241 Close(connfd); 242 exit(0); 243 //静态页面输出 244 } 245 */ 246 } 247 248 /* 249 * 检查一些明显的错误,报告给客户端 250 */ 251 void clienterror(int fd, char *cause, char *errnum, char *shortmsg, 252 char *longmsg) { 253 char buf[MAXLINE], body[MAXBUF]; 254 255 /* 构建HTTP response 响应主体 */ 256 sprintf(body, "<html><title>Flyuz Error</title>"); 257 sprintf(body, 258 "%s<body bgcolor=" 259 "white" 260 ">\r\n", 261 body); 262 sprintf(body, "%s<center><h1>%s: %s</h1></center>", body, errnum, shortmsg); 263 sprintf(body, "%s<center><h3>%s: %s</h3></center>", body, longmsg, cause); 264 sprintf(body, "%s<hr><center>The Flyuzy Web server</center>\r\n", body); 265 266 /* 打印HTTP响应报文 */ 267 sprintf(buf, "HTTP/1.0 %s %s", errnum, shortmsg); 268 Rio_writen(fd, buf, strlen(buf)); 269 sprintf(buf, "Content-type: text/html\r\n"); 270 Rio_writen(fd, buf, strlen(buf)); 271 sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body)); 272 Rio_writen(fd, buf, strlen(buf)); 273 Rio_writen(fd, body, strlen(body)); 274 }
1 /* 2 * @filename: functionLib.h 3 * @author: Flyuz 4 * @date: 2018年6月25日 5 * @description: 函数实现 6 */ 7 8 #ifndef ALL_H 9 #define ALL_H 10 11 #include <stdio.h> 12 #include <string.h> 13 #include <stdlib.h> 14 #include <unistd.h> 15 #include <ctype.h> 16 #include <netinet/in.h> 17 #include <arpa/inet.h> 18 #include <sys/socket.h> 19 #include <sys/types.h> 20 #include <sys/wait.h> 21 #include <errno.h> 22 #include <fcntl.h> 23 #include <signal.h> 24 #include <sys/stat.h> 25 #include <sys/mman.h> 26 27 #define RIO_BUFSIZE 8192 28 #define MAXLINE 8192 /* 一行最大长度 */ 29 typedef struct { 30 int rio_fd; //内部读缓冲区描述符 31 int rio_cnt; //内部缓冲区中未读字节数 32 char *rio_bufp; //内部缓冲区中下一个未读的字节 33 char rio_buf[RIO_BUFSIZE]; //内部读缓冲区 34 }rio_t; //一个类型为rio_t的读缓冲区 35 36 #define MAXLINE 8192 37 #define MAXBUF 8192 38 #define LISTENQ 1024 39 40 typedef struct sockaddr SA; 41 typedef void handler_t(int); 42 void unix_error(char *msg); 43 44 /* Process control wrappers */ 45 pid_t Fork(); 46 void Execve(const char *filename, char *const argv[], char *const envp[]); 47 pid_t Wait(int *status); 48 49 /* Unix I/O */ 50 int Open(const char *pathname, int flags, mode_t mode); 51 void Close(int fd); 52 int Dup2(int oldfd, int newfd); 53 54 55 56 void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); 57 void Munmap(void *start, size_t length); 58 59 /* RIO package */ 60 ssize_t rio_readn(int fd, void *usrbuf, size_t n); 61 ssize_t rio_writen(int fd, void *usrbuf, size_t n); 62 void rio_readinitb(rio_t *rp, int fd); 63 ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); 64 ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n); 65 66 67 ssize_t Rio_readn(int fd, void *usrbuf, size_t n); 68 void Rio_writen(int fd, void *usrbuf, size_t n); 69 void Rio_readinitb(rio_t *rp, int fd); 70 ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); 71 ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n); 72 73 74 int open_listenfd(char *port); 75 76 int Open_listenfd(char *port); 77 78 int Accept(int s, struct sockaddr *addr, socklen_t *addrlen); 79 80 handler_t *signal_r(int signum, handler_t *handler); 81 void sigchild_handler(int sig); 82 83 #endif
1 /* 2 * @filename: functionLib.c 3 * @author: Flyuz 4 * @date: 2018年6月25日 5 * @description: 辅助函数 6 */ 7 8 #include "functionLib.h" 9 10 void unix_error(char *msg) 11 { 12 fprintf(stderr, "%s: %s\n",msg, strerror(errno)); 13 exit(0); 14 } 15 16 pid_t Fork() 17 { 18 pid_t pid; 19 pid = fork(); 20 if(pid < 0) 21 unix_error("fork error"); 22 return pid; 23 } 24 25 void Execve(const char *filename, char *const argv[], char *const envp[]) 26 { 27 if(execve(filename, argv,envp) < 0) 28 unix_error("Execve error"); 29 } 30 31 pid_t Wait(int *status) 32 { 33 pid_t pid; 34 35 if((pid = wait(status)) < 0) 36 unix_error("Wait error"); 37 return pid; 38 } 39 40 41 42 int Open(const char *pathname, int flags, mode_t mode) 43 { 44 int rc; 45 46 if((rc = open(pathname, flags, mode)) < 0) 47 unix_error("Open error"); 48 return rc; 49 } 50 51 int Accept(int s, struct sockaddr *addr, socklen_t *addrlen) 52 { 53 int rc; 54 55 if((rc = accept(s, addr,addrlen)) < 0) 56 unix_error("Accept error"); 57 return rc; 58 } 59 60 void Close(int fd) 61 { 62 int rc; 63 64 if((rc = close(fd)) < 0) 65 unix_error("Close error"); 66 } 67 68 int Dup2(int oldfd, int newfd) 69 { 70 int rc; 71 72 if((rc = dup2(oldfd, newfd)) < 0 ) 73 unix_error("Dup2 error"); 74 return rc; 75 } 76 77 78 void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) 79 { 80 void *ptr; 81 82 if((ptr = mmap(addr, len, prot, flags, fd, offset)) == ((void *)-1)) 83 unix_error("Mmap error"); 84 return ptr; 85 } 86 87 void Munmap(void *start, size_t length) 88 { 89 if(munmap(start, length) < 0) 90 unix_error("Munmap error"); 91 } 92 /* 93 * 无缓冲输入函数 94 * 成功返回收入的字节数 95 * 若EOF返回0 ,出错返回-1 96 */ 97 ssize_t rio_readn(int fd, void *usrbuf, size_t n) 98 { 99 char *bufp = usrbuf; 100 size_t nleft = n; 101 ssize_t nread; 102 while(nleft > 0) { 103 if((nread = read(fd,bufp,nleft)) < 0) { 104 if(errno == EINTR) 105 nread = 0; 106 else 107 return -1; 108 } else if( nread == 0 ) 109 break; 110 nleft -= nread; 111 bufp += nread; 112 } 113 return (n-nleft); 114 } 115 /* 116 * 无缓冲输出函数 117 * 成功返回输出的字节数,出错返回-1 118 */ 119 ssize_t rio_writen(int fd, void *usrbuf, size_t n) 120 { 121 char *bufp = usrbuf; 122 size_t nleft = n; 123 ssize_t nwritten; 124 while(nleft > 0) { 125 if((nwritten = write(fd, bufp, nleft)) <= 0) { 126 if(errno == EINTR) 127 nwritten = 0; 128 else 129 return -1; 130 } 131 nleft -= nwritten; 132 bufp += nwritten; 133 } 134 return n; 135 } 136 137 138 static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n) 139 { 140 while(rp -> rio_cnt <= 0) { 141 rp -> rio_cnt = read(rp -> rio_fd, rp -> rio_buf, 142 sizeof(rp -> rio_buf)); 143 if(rp -> rio_cnt < 0) { 144 if(errno != EINTR) 145 return -1; 146 } else if(rp -> rio_cnt == 0) 147 return 0; 148 else 149 rp -> rio_bufp = rp -> rio_buf; 150 } 151 152 int cnt = rp -> rio_cnt < n ? rp -> rio_cnt : n; 153 154 memcpy(usrbuf, rp -> rio_bufp, cnt); 155 rp -> rio_bufp += cnt; 156 rp -> rio_cnt -= cnt; 157 return cnt; 158 } 159 // 初始化rio_t结构,创建一个空的读缓冲区 160 // 将fd和地址rp处的这个读缓冲区联系起来 161 void rio_readinitb(rio_t *rp, int fd) 162 { 163 rp -> rio_fd = fd; 164 rp -> rio_cnt = 0; 165 rp -> rio_bufp = rp -> rio_buf; 166 } 167 168 //带缓冲输入函数 169 ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n) 170 { 171 size_t nleft = n; 172 ssize_t nread; 173 char *bufp = usrbuf; 174 175 while(nleft > 0) { 176 if((nread = rio_read(rp, bufp, nleft)) < 0) 177 return -1; 178 else if (nread == 0) 179 break; 180 181 nleft -= nread; 182 bufp += nread; 183 } 184 return (n-nleft); 185 } 186 /* 187 **带缓冲输入函数,每次输入一行 188 **从文件rp读出一个文本行(包括结尾的换行符),将它拷贝到usrbuf,并且用空字符来结束这个文本行 189 **最多读maxlen-1个字节,余下的一个留给结尾的空字符 190 **/ 191 ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) 192 { 193 int n, rc; 194 char c, *bufp = usrbuf; 195 for(n = 1; n<maxlen; n++) { 196 if((rc = rio_read(rp, &c, 1)) == 1) { 197 *bufp++ = c; 198 if( c == '\n' ){ 199 n++; 200 break; 201 } 202 } else if(rc == 0) { 203 if( n == 1 ) 204 return 0; 205 else 206 break; 207 } else 208 return -1; 209 } 210 *bufp = '\0'; 211 return n-1; 212 } 213 214 ssize_t Rio_readn(int fd, void *usrbuf, size_t n) 215 { 216 ssize_t nbytes; 217 if((nbytes = rio_readn(fd, usrbuf, n)) < 0) 218 unix_error("Rio_readn error"); 219 220 return nbytes; 221 } 222 223 void Rio_writen(int fd, void *usrbuf, size_t n) 224 { 225 if(rio_writen(fd, usrbuf, n) != n) 226 unix_error("Rio_writen error"); 227 } 228 229 void Rio_readinitb(rio_t *rp, int fd) 230 { 231 rio_readinitb(rp, fd); 232 } 233 234 ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) 235 { 236 ssize_t nbytes; 237 if((nbytes = rio_readlineb(rp, usrbuf, maxlen)) < 0) 238 unix_error("Rio_readlineb error"); 239 return nbytes; 240 } 241 242 ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n) 243 { 244 ssize_t nbytes; 245 if((nbytes = rio_readnb(rp, usrbuf, n)) < 0) 246 unix_error("Rio_readnb error"); 247 return nbytes; 248 } 249 250 /*打开并返回监听描述符*/ 251 int open_listenfd(char *port) 252 { 253 int listenfd, optval = 1; 254 struct sockaddr_in serveraddr; 255 256 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ 257 return -1; 258 } 259 260 if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, 261 (const void *)&optval, sizeof(int)) < 0) 262 return -1; 263 264 bzero((char *)&serveraddr, sizeof(serveraddr)); 265 266 serveraddr.sin_family = AF_INET; 267 serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 268 serveraddr.sin_port = htons((unsigned short)atoi(port)); 269 270 if(bind(listenfd, (SA *)&serveraddr, sizeof(serveraddr)) < 0) 271 return -1; 272 273 if(listen(listenfd, LISTENQ) < 0) 274 return -1; 275 276 return listenfd; 277 } 278 279 int Open_listenfd(char *port) 280 { 281 int rc; 282 if((rc = open_listenfd(port)) < 0) 283 unix_error("open_listenfd error"); 284 return rc; 285 } 286 //信号处理 287 handler_t *signal_r(int signum, handler_t *handler) 288 { 289 struct sigaction action, old_action; 290 291 action.sa_handler = handler; 292 sigemptyset(&action.sa_mask); 293 action.sa_flags = SA_RESTART; 294 295 if (sigaction(signum, &action, &old_action) < 0) 296 perror("Signal error"); 297 return (old_action.sa_handler); 298 } 299 //排队 防止阻塞 300 void sigchild_handler(int sig) 301 { 302 int stat; 303 while(waitpid(-1,&stat,WNOHANG)>0); 304 return; 305 }