转载请注明出处
http://blog.csdn.net/pony_maggie/article/details/49838191
作者:小马
继续看execute_cgi函数,
if (strcasecmp(method, "GET") ==0) { while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); } else /* POST */ { numchars = get_line(client, buf, sizeof(buf)); while ((numchars > 0) && strcmp("\n", buf)) { buf[15] = '\0'; if(strcasecmp(buf, "Content-Length:") == 0) content_length = atoi(&(buf[16])); numchars = get_line(client, buf, sizeof(buf)); } if (content_length == -1) // { bad_request(client);// 4xx: return; } }
下面一段建立两个管道,
if (pipe(cgi_output) < 0) { cannot_execute(client); return; } if(pipe(cgi_input) < 0) { cannot_execute(client); return; }
if ( (pid = fork()) < 0 ) //复制一个线程 { cannot_execute(client); return; }
那么问题来了,如何区分当前执行的是父进程还是子进程呢?fork函数如果调用出错会返回小于0的值。如果成功了,它会返回两次,一次是父进程中返回子进程的PID,一次是在子进程中返回0。这两个进程并发的执行,系统内核层是以时间片切换来模拟多进程的执行,不过作为程序员我们认为有两条线在并行的运行就可以了。
所以,通过判断返回的PID,就可以分别对子进程和父进程进行操作。在开始讲子进程之前,还需要插播一个概念:
CGI程序的特点是通过标准输入(stdin)和环境变量(可以理解成有两个传递数据的途径,二者相辅相成,其实跟请求方法是get或post也相关)来得到服务器的信息,并通过标准输出(stdout)向服务器输出信息。环境变量就是指的系统环境变量,就是linux中可以用echo命令查询那些,比如HOME, PATH等。http协议中也定义了一些自己的环境变量,用于在CGI与服务器之间传输参数。下面给出几个相关的环境变量:
SERVER_NAME CGI脚本运行时的主机名和IP地址. SERVER_SOFTWARE 你的服务器的类型如: CERN/3.0 或 NCSA/1.3. GATEWAY_INTERFACE 运行的CGI版本.对于UNIX服务器, 这是CGI/1.1. SERVER_PROTOCOL 服务器运行的HTTP协议. 这里当是HTTP/1.0. SERVER_PORT 服务器运行的TCP口,通常Web服务器是80. REQUEST_METHOD POST 或 GET, 取决于你的表单是怎样递交的. HTTP_ACCEPT 浏览器能直接接收的Content-types, 可以有HTTP Accept header定义. HTTP_USER_AGENT 递交表单的浏览器的名称、版本 和其他平台性的附加信息。 HTTP_REFERER 递交表单的文本的 URL,不是所有的浏览器都发出这个信息,不要依赖它 PATH_INFO 附加的路径信息, 由浏览器通过GET方法发出. PATH_TRANSLATED 在PATH_INFO中系统规定的路径信息. SCRIPT_NAME 指向这个CGI脚本的路径,是在URL中显示的(如, /cgi-bin/thescript). QUERY_STRING 脚本参数或者表单输入项(如果是用GET递交). QUERY_STRING 包含URL中问号后面的参数. REMOTE_HOST 递交脚本的主机名,这个值不能被设置. REMOTE_ADDR 递交脚本的主机IP地址. REMOTE_USER 递交脚本的用户名. 如果服务器的authentication被激活,这个值可以设置。 REMOTE_IDENT 如果Web服务器是在ident(一种确认用户连接你的协议)运行,递交表单的系统也在运行ident, 这个变量就含有ident返回值. CONTENT_TYPE 如果表单是用POST递交, 这个值将是application/x-www-form-urlencoded. 在上载文件的表单中,content-type 是个multipart/form-data. CONTENT_LENGTH 对于用POST递交的表单, 标准输入口的字节数.
看看子进程,
if (pid == 0) /* child: CGI script */ { …. dup2(cgi_output[1], 1);///* 把 STDOUT 重定向到cgi_output 的写入端 */ dup2(cgi_input[0], 0); ///* 把 STDIN 重定向到cgi_input 的读取端 */ ///* 关闭 cgi_input 的写入端和 cgi_output 的读取端 */ close(cgi_output[0]); close(cgi_input[1]);
当然,重定向完成后要把不用的描述符关掉,注意这里的不用是仅子进程不用,前面说到fork出来的子进程会拥有一份和父进程几乎一样的资源拷贝,所以这里只是把子进程空间中的描述符资源关闭。
继续看代码,
sprintf(meth_env, "REQUEST_METHOD=%s", method); putenv(meth_env); if(strcasecmp(method, "GET") == 0) { sprintf(query_env, "QUERY_STRING=%s", query_string); putenv(query_env); } else { /* POST*/ sprintf(length_env, "CONTENT_LENGTH=%d", content_length); putenv(length_env); }
准备工作都作完了,可以调用cgi程序了,cgi所在的路径是前面保存的地址,还记得吧。
execl(path, path, NULL); exit(0); }
再来看看父进程的代码,
close(cgi_output[1]); close(cgi_input[0]); if(strcasecmp(method, "POST") == 0) for (i = 0; i < content_length; i++) { recv(client, &c, 1, 0); write(cgi_input[1], &c, 1);/*把 POST 数据写入cgi_input,已经重定向到 STDIN */ } while (read(cgi_output[0], &c, 1) > 0)/*读取 cgi_output的管道输出到客户端,该管道输入是 STDOUT */ send(client, &c, 1, 0); close(cgi_output[0]); close(cgi_input[1]); waitpid(pid, &status, 0);
整个子进程和父进程的处理过程,可以用下面图解释(用的别人的图,出处是:http://blog.csdn.net/jcjc918/article/details/42129311)
最后回到accept_request函数,最后一行是
close(client);
这个有必要说一下,http是基于无连接的,也就是完成一次请求后,马上断开与客户端的连接,所以一定要有这个关闭的动作。