Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
2016-11-04 14:05
941 查看
FastCGI编程包括四部分:初始化编码、接收请求循环、响应内容、响应结束循环。
由于最近工作的需要,本人学习了一下利用高性能webserver-Nginx,来发布C/C++编写的fastCGI程序,详细细节如下。
1.介绍
Nginx-高性能webserver,这个不用多说了,大家都知道。
FastCGI程序-常驻型CGI程序,它是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中并因此获得较高的性能。
Nginx要调用FastCGI程序,需要用到FastCGI进程管理程序(因为nginx不能直接执行外部的cgi程序,我们可使用lighttpd中的spawn-fastcgi来让nginx可支持外部cgi运行。也有其他方法安装nginx-fcgi来让nginx支持cgi,这里是使用spawn-fastcgi的方法),来达到调用FastCGI程序的目的。Nginx本身没有集成类似的模块,而Apache具备该功能模块,所以不需要额外安装FastCGI进程管理程序。
2.工作原理
Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket(这个socket可以是文件socket,也可以是ipsocket)。为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。
当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接收到请求,然后派生出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据发送给客户端。这就是Nginx+FastCGI的整个运作过程,如图1所示。
图1Nginx+FastCGI运行过程
FastCGI接口方式在脚本解析服务器(CGI应用程序服务器)上启动一个或者多个守护进程对动态脚本进行解析,这些进程就是FastCGI进程管理器,或者称为FastCGI引擎。spawn-fcgi与PHP-FPM都是FastCGI进程管理器(支持PHP和C/C++)。
介绍到这里,大家应该都对该模式有了一定的了解,下面开始进行实战!
3.环境部署
3.1.Nginx的安装、部署与配置
nginx下载目录http://nginx.org/en/download.html这我们使用的是nginx-1.5.10
[安装]
下载以后解压并安装(请记得看README)
./configure(注意了类似checkingfor***...notfound项,可能是依赖包没有,则需要安装依赖包)
缺少pcre,则需要额外安装http://www.pcre.org/(或者采用apt-get或yum的安装方式)
缺少zlib,则需要额外安装http://www.zlib.net/(或者采用apt-get或yum的安装方式)
缺少OpenSSL,则需要额外安装http://www.openssl.org(或者采用apt-get或yum的安装方式)
如果需要配置安装额外的功能模块,可以参考这里http://wiki.codemongers.com/NginxChsInstall
make
makeinstall(默认安装到/usr/local/nginx)
[配置和管理]
1)执行选项
-c</path/to/config>为Nginx指定一个配置文件,来代替缺省的。不输入则使用默认的配置文件。
-t不运行,而仅仅测试配置文件。nginx将检查配置文件的语法的正确性,并尝试打开配置文件中所引用到的文件。
-v显示nginx的版本。
-V显示nginx的版本,编译器版本和配置参数。
2)检查配置文件
sudo./nginx-t
nginx:theconfigurationfile/usr/local/nginx/conf/nginx.confsyntaxisok
nginx:configurationfile/usr/local/nginx/conf/nginx.conftestissuccessful
3)启动-默认和特殊
/usr/local/nginx/sbin/nginx(默认启动方式)
/usr/local/nginx/sbin/nginx-c/usr/local/nginx/conf/nginx.conf(指定配置文件启动)
4)查看nginx进程号(master是主进程)
ps-ef|grepnginx
5)重新加载配置文件
sudokill-HUP[nginx主进程号]
通过系统的信号控制Nginx
可以使用信号系统来控制主进程。默认,nginx将其主进程的pid写入到/usr/local/nginx/logs/nginx.pid文件中。通过传递参数给./configure或使用pid指令,来改变该文件的位置。
主进程可以处理以下的信号:
命令
说明备注
TERM,INT快速关闭
QUIT从容关闭
HUP重载配置
用新的配置开始新的工作进程从容关闭旧的工作进程
USR1重新打开日志文件
USR2平滑升级可执行程序
WINCH从容关闭工作进程
6)默认目录结构
主目录:/usr/local/nginx/
配置目录:/usr/local/nginx/conf/
root目录:/usr/local/nginx/html/
可执行文件路径:/usr/local/nginx/sbin/
3.2.spawn_fastcgi的安装、部署与配置
spawn_fastcgihttps://github.com/lighttpd/spawn-fcgi这里使用的是1.6.3的版本https://github.com/lighttpd/spawn-fcgi/releases/tag/v1.6.3
下载以后解压并安装(请记得看README)
如果没有configure,请先执行./autogen.sh,生成configure
./configure
make
编译好以后,将可执行文件移动到nginx的sbin目录下
cp./src/spawn-fcgi/usr/local/nginx/sbin/(cp到nginx的安装目录下)
3.3.fastcgi库的安装(库绝对不是必须的,觉得技术好的大牛可以自己写)
库地址http://www.fastcgi.com/dist/fcgi.tar.gz
下载以后,解压并安装(默认安装)
./configure
make
makeinstall
4.Demo和web发布
4.1.Demo程序
[CGI程序]
[cpp]viewplaincopy
#include<fcgi_stdio.h>
#include<stdlib.h>
intmain(){
intcount=0;
while(FCGI_Accept()>=0){
printf("Content-type:text/html\r\n"
"\r\n"
""
"FastCGIHello!"
"Requestnumber%drunningonhost%s"
"ProcessID:%d\n",++count,getenv("SERVER_NAME"),getpid());
}
return0;
}
[编译]
g++demo.cc-odemo-lfcgi
直接运行可执行文件,看看能否正常运行。如果出现缺少库libfcgi.so.0,则自己需要手动把/usr/local/lib/libfcgi.so.0库建立一个链接到/usr/lib/目录下:ln-s/usr/local/libfcgi.so.0/usr/lib/(或者把so的库路径添加到/etc/ld.so.conf,并执行ldconfig更新一下)
4.2.Web发布
1)将CGI可执行程序移动到nginx的安装目录下/usr/local/nginx/cgibin(文件夹不存在则自己创建)
2)启动spawn-fcgi管理进程,并绑定serverIP和端口(不要跟nginx的监听端口重合)
/usr/local/nginx/sbin/spawn-fcgi-a127.0.0.1-p8088-f/usr/local/nginx/cgibin/demo
查看一下9002端口是否已成功:netstat-na|grep8088
3)更改nginx.conf配置文件,让nginx转发请求
在http节点的子节点-"server节"点中下添加配置
location~\.cgi${
fastcgi_pass127.0.0.1:8088;
fastcgi_indexindex.cgi;
fastcgi_paramSCRIPT_FILENAMEfcgi$fastcgi_script_name;
includefastcgi_params;
}
4)重启nginx或者重新加载配置文件
重新加载配置文件
sudokill-HUP[pid]
或者
重启nginx
killallnginx
./nginx
5)打开浏览器访问一下吧
http://localhost/demo.cgi
搞定收工,心里又小小的激动了一把!
allenrlin
2014/2/18
参考文献与资料
[1]Nginx+FastCGI运行原理http://book.51cto.com/art/201202/314840.htm[2]Nginx下配置FastCGIhttp://www.cppblog.com/woaidongmao/archive/2011/06/21/149090.html[3]nginx+fastcgi+c/c++搭建高性能Web框架http://blog.csdn.net/marising/article/details/3932938
[4]什么是CGI、FastCGI、PHP-CGI、PHP-FPM、Spawn-FCGIhttp://www.mike.org.cn/articles/what-is-cgi-fastcgi-php-fpm-spawn-fcgi/
接着上篇《Nginx安装与使用》,本篇介绍CGI/FASTCGI的原理、及如何使用C/C++编写简单的CGI/FastCGI,最后将CGI/FASTCGI部署到nginx。内容大纲如下:
1.CGI
1.1.环境变量
1.2.标准输入
2.FastCGI
3.nginxcgi/fastcgi
3.1.nginx+fastcgi
3.1.1.spawn-fcgi
3.1.2.编写fastcgi应用程序
3.1.3.nginxfastcgi配置
3.2.nginx+cgi
3.2.1fastcgi-wrapper
3.2.2.nginxfcgiwrap配置
3.2.3.编写cgi应用程序
参考链接
脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行。Unixshellscript,Python,Ruby,PHP,perl,Tcl,C/C++,和VisualBasic都可以用来编写CGI程序。(http://www.dwz.cn/yFFgQ)
最初,CGI是在1993年由美国国家超级电脑应用中心(NCSA)为NCSAHTTPdWeb服务器开发的。这个Web服务器使用了UNIXshell环境变量来保存从Web服务器传递出去的参数,然后生成一个运行CGI的独立的进程。cgi的处理流程如下图所示:
lstep1.web服务器收到客户端(浏览器)的请求HttpRequest,启动CGI程序,并通过环境变量、标准输入传递数据
lstep2.cgi进程启动解析器、加载配置(如业务相关配置)、连接其它服务器(如数据库服务器)、逻辑处理等
lstep3.cgi程将处理结果通过标准输出、标准错误,传递给web服务器
lstep4.web服务器收到cgi返回的结果,构建HttpResponse返回给客户端,并杀死cgi进程
web服务器与cgi通过环境变量、标准输入、标准输出、标准错误互相传递数据。
总结:CGI使外部程序与Web服务器之间交互成为可能。CGI程式运行在独立的进程中,并对每个Web请求建立一个进程,这种方法非常容易实现,但效率很差,难以扩展。面对大量请求,进程的大量建立和消亡使操作系统性能大大下降。此外,由于地址空间无法共享,也限制了资源重用。
通用网关接口(CGI)的改进,描述了客户端和服务器程序之间传输数据的一种标准。FastCGI致力于减少Web服务器与CGI程式之间互动的开销,从而使服务器可以同时处理更多的Web请求。与为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI进程管理器管理,而不是web服务器。(http://www.dwz.cn/yFMap)
当进来一个请求时,Web服务器把环境变量和这个页面请求通过一个unixdomainsocket(都位于同一物理服务器)或者一个IPSocket(FastCGI部署在其它物理服务器)传递给FastCGI进程。
lstep1.Web服务器启动时载入初始化FastCGI执行环境。例如IISISAPI、apachemod_fastcgi、nginxngx_http_fastcgi_module、lighttpdmod_fastcgi
lstep2.FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX域socket两种方式启动。
lstep3.当客户端请求到达Web服务器时,Web服务器将请求采用socket方式转发到FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器。Web服务器将CGI环境变量和标准输入发送到FastCGI子进程。
lstep4.FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web服务器。当FastCGI子进程关闭连接时,请求便处理完成。
lstep5.FastCGI子进程接着等待并处理来自Web服务器的下一个连接。
由于FastCGI程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的应用效率。它的速度效率最少要比CGI技术提高5倍以上。它还支持分布式的部署,即FastCGI程序可以在web服务器以外的主机上执行。
总结:CGI就是所谓的短生存期应用程序,FastCGI就是所谓的长生存期应用程序。FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute模式)。
安装spawn-fcgi:
l获取spawn-fcgi编译安装包,在http://redmine.lighttpd.net/projects/spawn-fcgi/wiki上可以获取当前最新的版本。
l解压缩spawn-fcgi-x.x.x.tar.gz包。
l进入解压缩目录,执行./configure。
lmake&makeinstall
如果遇到以下错误:“./autogen.sh:x:autoreconf:notfound”,因为没有安装automake工具,ubuntu用下面的命令安装好就可以了:sudoapt-getinstallautoconfautomakelibtool。
spawn-fcgi的帮助信息可以通过manspawn-fcgi或spawn-fcgi–h获得,下面是部分常用spawn-fcgi参数信息:
本文使用FastCGI软件开发套件——fcgi(http://www.fastcgi.com/drupal/node/6?q=node/21),通过此套件可以轻松编写fastcgi应用程序,安装fcgi:
l获取fcgi编译安装包,在http://www.fastcgi.com/drupal/node/5上可以获取当前最新的版本。
l解压缩fcgi-x.x.x.tar.gz包。
l进入解压缩目录,执行./configure。
lmake&makeinstall
如果编译提示一下错误:
fcgio.cpp:Indestructor'virtualfcgi_streambuf::~fcgi_streambuf()':
make:***[all]Error2
解决办法:在/include/fcgio.h文件中加上#include<cstdio>,然后再编译安装就通过了。
如果提示找不到动态库,请在LD_LIBRARY_PATH或/etc/ld.so.conf中添加fcgi的安装路径,如/usr/local/lib,并执行ldconfig更新一下。
#include"fcgi_stdio.h"
#include<stdlib.h>
intmain(void)
{
intcount=0;
while(FCGI_Accept()>=0)
printf("Content-type:text/html\r\n"
"\r\n"
"<title>FastCGIHello!</title>"
"<h1>FastCGIHello!</h1>"
"Requestnumber%drunningonhost<i>%s</i>\n",
++count,getenv("SERVER_NAME"));
return0;
编译g++main.cpp-odemo–lfcgi,并将demo部署到/opt/nginx-1.7.7/cgi-bin/目录
通过spawn-fcgi启动c/c++编写好的fastcgi程序:/opt/nginx-1.7.7/sbin/spawn-fcgi-a127.0.0.1-p8081-f/opt/nginx-1.7.7/cgi-bin/demo
Nginx安装与使用》http://www.cnblogs.com/skynet/p/4146083.html,在上篇的nginx.conf基础上增加下面的fastcgi配置。
这样nginx收到http://localhost/demo.cgi请求时,会匹配到location=/demo.cgi块,将请求传到后端的fastcgi应用程序处理。如下如所示:(注意其中number为80,是因为我请求了80次)
明白原理之后,编写一个fastcgi-warpper也比较简单。网上流传比较多的一个解决方案是,来自nginxwiki(http://wiki.nginx.org/SimpleCGI)上的使用perl的fastcgi包装脚本cgiwrap-fcgi.pl。但我对perl不是很感冒,下面给出一个C/C++写的fastcgi-wrapper。
https://github.com/gnosek/fcgiwrap。
安装fcgiwrap:
l下载(https://github.com/gnosek/fcgiwrap.git)
l解压缩fcgiwrap,进入解压目录
lautoreconf-i
l./configure
lmake&&makeinstall
启动fastcgi-wrapper:/opt/nginx-1.7.7/sbin/spawn-fcgi-f/usr/local/sbin/fcgiwrap-p8081
#include<stdlib.h>
intmain(void)
{
intcount=0;
printf("Content-type:text/html\r\n"
"\r\n"
"<title>CGIHello!</title>"
"<h1>CGIHello!</h1>"
"Requestnumber%drunningonhost<i>%s</i>\n",
++count,getenv("SERVER_NAME"));
return0;
}
tyler@ubuntu:~/ClionProjects/HelloFastCGI$g++cgi.cpp-ocgidemo-lfcgi
tyler@ubuntu:~/ClionProjects/HelloFastCGI$sudocpcgidemo/opt/nginx-1.7.7/cgi-bin/
注意图中的请求次数一直都是1,因为cgi的模式是fork-and-exec,每次都是一个新的进程。
http://www.dwz.cn/yFFgQ
lfastcgi,http://www.dwz.cn/yFMap
lspawn-fcgi,http://redmine.lighttpd.net/projects/spawn-fcgi/wiki
lfcgi,http://www.fastcgi.com/drupal/node/6?q=node/21
lfcgiwrap,https://github.com/gnosek/fcgiwrap.git
spawn-fcgi是一个小程序,作用是管理fast-cgi进程,功能和PHP-fpm类似,简单小巧,原先是属于lighttpd的一部分,后来由于使用比较广泛,所以就迁移出来作为独立项目了,本文介绍的是这个版本“spawn-fcgi-1.6.3”。不过从发布新版本到目前已经4年了,代码一直没有变动,需求少,基本满足了。另外php有php-fpm后,码农们再也不担心跑不起FCGI了。
很久之前看的spawn-fcgi的代码,当时因为需要改一下里面的环境变量。今天翻代码看到了就顺手记录一下,就当沉淀.备忘吧。
用spawn启动FCGI程序的方式为:./spawn-fcgi-a127.0.0.1-p9003-F${count}-f${webroot}/bin/demo.fcgi
这样就会启动count个demo.fcgi程序,他们共同监听同一个listen端口9003,从而提供服务。
spawn-fcgi代码不到600行,非常简短精炼,从main看起。其功能主要是打开监听端口,绑定地址,然后fork-exec创建FCGI进程,退出完成工作。
老方法,main函数使用getopt解析命令行参数,从而设置全局变量。如果设置了-P参数,需要保存Pid文件,就用open系统调用打开文件。之后根据是否是root用户启动,如果是root,得做相关的权限设置,比如chroot,chdir,setuid,setgid,setgroups等。
重要的是调用了bind_socket打开绑定本地监听地址,或者sock,再就是调用fcgi_spawn_connection创建FCGI进程,主要就是这2步。
[cpp]viewplaincopy
intmain(intargc,char**argv)
{
if(!sockbeforechroot&&-1==(fcgi_fd=bind_socket(addr,port,unixsocket,sockuid,sockgid,sockmode)))
return-1;
/*droprootprivs*/
if(uid!=0)
{
setuid(uid);
}
else//非root用户启动,打开监听端口,进入listen模式。
{
if(-1==(fcgi_fd=bind_socket(addr,port,unixsocket,0,0,sockmode)))
return-1;
}
if(fcgi_dir&&-1==chdir(fcgi_dir))
{
fprintf(stderr,"spawn-fcgi:chdir('%s')failed:%s\n",fcgi_dir,strerror(errno));
return-1;
}
//fork创建FCGI的进程
returnfcgi_spawn_connection(fcgi_app,fcgi_app_argv,fcgi_fd,fork_count,child_count,pid_fd,nofork);
}
bind_socket函数用来创建套接字,绑定监听端口,进入listen模式。其参数unixsocket表明需要使用unixsock文件,这里不多介绍。函数代码页挺简单,莫过于通用的sock程序步骤:socket()->setsockopt()->bind()->listen();
[cpp]viewplaincopy
staticintbind_socket(constchar*addr,unsignedshortport,constchar*unixsocket,uid_tuid,gid_tgid,intmode)
{
//bind_socket函数用来创建套接字,绑定监听端口,进入listen模式
if(-1==(fcgi_fd=socket(socket_type,SOCK_STREAM,0)))
{
fprintf(stderr,"spawn-fcgi:couldn'tcreatesocket:%s\n",strerror(errno));
return-1;
}
val=1;
if(setsockopt(fcgi_fd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))<0)
{
fprintf(stderr,"spawn-fcgi:couldn'tsetSO_REUSEADDR:%s\n",strerror(errno));
return-1;
}
if(-1==bind(fcgi_fd,fcgi_addr,servlen))
{
fprintf(stderr,"spawn-fcgi:bindfailed:%s\n",strerror(errno));
return-1;
}
if(unixsocket)
{
if(0!=uid||0!=gid)
{
if(0==uid)uid=-1;
if(0==gid)gid=-1;
if(-1==chown(unixsocket,uid,gid))
{
fprintf(stderr,"spawn-fcgi:couldn'tchownsocket:%s\n",strerror(errno));
close(fcgi_fd);
unlink(unixsocket);
return-1;
}
}
if(-1!=mode&&-1==chmod(unixsocket,mode))
{
fprintf(stderr,"spawn-fcgi:couldn'tchmodsocket:%s\n",strerror(errno));
close(fcgi_fd);
unlink(unixsocket);
return-1;
}
}
if(-1==listen(fcgi_fd,1024))
{
fprintf(stderr,"spawn-fcgi:listenfailed:%s\n",strerror(errno));
return-1;
}
returnfcgi_fd;
}
fcgi_spawn_connection函数的工作是循环一次次创建子进程,然后立即调用execv(appArgv[0],appArgv);替换可执行程序,也就试运行demo.fcgi。
[cpp]viewplaincopy
staticintfcgi_spawn_connection(char*appPath,char**appArgv,intfcgi_fd,intfork_count,intchild_count,intpid_fd,
intnofork)
{
intstatus,rc=0;
structtimevaltv={0,100*1000};
pid_tchild;
while(fork_count-->0)
{
if(!nofork)//正常不会设置nofork的
{
child=fork();
}
else
{
child=0;
}
switch(child)
{
case0:
{
//子进程
charcgi_childs[64];
intmax_fd=0;
inti=0;
if(child_count>=0)
{
snprintf(cgi_childs,sizeof(cgi_childs),"PHP_FCGI_CHILDREN=%d",child_count);
putenv(cgi_childs);
}
//wuhaiwen:addchildidtothread
charbd_children_id[32];
snprintf(bd_children_id,sizeof(bd_children_id),"BD_CHILDREN_ID=%d",fork_count);
putenv(bd_children_id);
if(fcgi_fd!=FCGI_LISTENSOCK_FILENO)
{
close(FCGI_LISTENSOCK_FILENO);
dup2(fcgi_fd,FCGI_LISTENSOCK_FILENO);
close(fcgi_fd);
}
/*loosecontrolterminal*/
if(!nofork)
{
setsid();//执行setsid()之后,parent将重新获得一个新的会话session组id,child将仍持有原有的会话session组,
//这时parent退出之后,将不会影响到child了[luther.gliethttp].
max_fd=open("/dev/null",O_RDWR);
if(-1!=max_fd)
{
if(max_fd!=STDOUT_FILENO)dup2(max_fd,STDOUT_FILENO);
if(max_fd!=STDERR_FILENO)dup2(max_fd,STDERR_FILENO);
if(max_fd!=STDOUT_FILENO&&max_fd!=STDERR_FILENO)close(max_fd);
}
else
{
fprintf(stderr,"spawn-fcgi:couldn'topenandredirectstdout/stderrto'/dev/null':%s\n",strerror
(errno));
}
}
/*wedon'tneedtheclientsocket*/
for(i=3;i<max_fd;i++)
{
if(i!=FCGI_LISTENSOCK_FILENO)close(i);
}
/*forkandreplaceshell*/
if(appArgv)//如果有外的参数,就用execv执行,否则直接用shell执行
{
execv(appArgv[0],appArgv);
}
else
{
char*b=malloc((sizeof("exec")-1)+strlen(appPath)+1);
strcpy(b,"exec");
strcat(b,appPath);
/*execthecgi*/
execl("/bin/sh","sh","-c",b,(char*)NULL);
}
/*innoforkmodestderrisstillopen*/
fprintf(stderr,"spawn-fcgi:execfailed:%s\n",strerror(errno));
exit(errno);
break;
}
}
}
上面是创建子进程的部分代码,基本没啥可说明的。
对于子进程:注意一下dup2函数,由子进程运行,将监听句柄设置为标准输入,输出句柄。比如FCGI_LISTENSOCK_FILENO0号在FCGI里面代表标准输入句柄。函数还会关闭其他不必要的socket句柄。
然后调用execv替换可执行程序,运行新的二进制,也就是demo.fcgi的FCGI程序。这样子进程能够继承父进程的所有打开句柄,包括监听socket。这样所有子进程都能够在这个9002端口上进行监听新连接,谁拿到了谁就处理之。
对于父进程:主要需要用select等待一会,然后调用waitpid用WNOHANG参数获取一下子进程的状态而不等待子进程退出,如果失败就打印消息。否则将其PID写入文件。
[cpp]viewplaincopy
default:
/*father*/
/*wait*/
select(0,NULL,NULL,NULL,&tv);
switch(waitpid(child,&status,WNOHANG))
{
case0:
fprintf(stdout,"spawn-fcgi:childspawnedsuccessfully:PID:%d\n",child);
/*writepidfile*/
if(pid_fd!=-1)
{
/*assumea32bitpid_t*/
charpidbuf[12];
snprintf(pidbuf,sizeof(pidbuf)-1,"%d",child);
write(pid_fd,pidbuf,strlen(pidbuf));
/*avoideolforthelastone*/
if(fork_count!=0)
{
write(pid_fd,"\n",1);
}
}
break;
基本就是上面的东西了,代码不多,但该有的都有,命令行解析,socket,fork,dup2等。很久之前看的在这里备忘一下。
cgi进程可以写成单线程的,也可以写成多线程的。
单线程就是main函数中有一个死循环,一直等待接受请求,有请求过来时,就处理请求,并返回结果,没有并发性。
多线程也分两种模式:一种是main函数起多个线程,每个线程都独立接受请求。另一种是main函数起一个accpet线程接受请求,多个do_session线程处理请求,这种模式需要一个任务队列的支持。
模式不同,采用的系统架构就不同。下面就这三种模型,给出编码架构设计。
单线程模式:
[cpp]viewplaincopy
#include<fcgi_stdio.h>
#include<stdlib.h>
#include<string>
intmain()
{
while(FCGI_Accept()>=0)
{
stringstrGetData;
intiContentLength;
if(getenv("QUERY_STRING"))
{
strGetData=getenv("QUERY_STRING");
}
if(getenv("CONTENT_LENGTH"))
{
iContentLength=::atoi(getenv("CONTENT_LENGTH"));
}
char*data=(char*)::malloc(iContentLength+1);
::memset(data,0,iContentLength+1);
FCGI_fgets(data,iContentLength+1,FCGI_stdin);
FCGI_printf("Content-type:text/html\r\n\r\n");
FCGI_printf(data);
}
return0;
}
多线程模式,每个线程都独立接受请求:
[cpp]viewplaincopy
#include<fcgi_stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string>
void*pthread_func(void*arg);
intmain()
{
intiThreadNum=10;
for(intindex=0;index!=iThreadNum;++index)
{
pthread_tpthread_id;
pthread_create(&pthread_id,NULL,pthread_func,NULL);
}
pthread_join();
return0;
}
void*pthread_func(void*arg)
{
FCGX_Request*request=NULL;
while(1)
{
intrc=FCGX_Accept_r(request);
if(rc<0)
{
continue;
}
stringstrRequestMethod;
stringstrGetData;
stringstrPostData;
intiPostDataLength;
if(FCGX_GetParam("QUERY_STRING",request->envp))
{
strGetData=FCGX_GetParam("QUERY_STRING",request->envp);
}
if(FCGX_GetParam("REQUEST_METHOD",request->envp))
{
iPostDataLength=::atoi(FCGX_GetParam("CONTENT_LENGTH",_pRequest->envp));
char*data=(char*)malloc(iPostDataLength+1);
::memset(data,0,iPostDataLength+1);
FCGX_GetStr(data,iPostDataLength,_pRequest->in);
strPostData=data;
free(data);
}
FCGX_PutS("Content-type:text/html\r\n\r\n",_pRequest->out);
FCGX_PutS(strPostData.c_str(),_pRequest->out);
}
}
多线程模式,一个accpet线程,多个do_session线程:
[cpp]viewplaincopy
#include<fcgi_stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string>
void*do_accept(void*arg);
void*do_session(void*arg);
intmain()
{
pthread_tpthread_id;
pthread_create(&pthread_id,NULL,do_accept,NULL);
intiThreadNum=10;
for(intindex=0;index!=iThreadNum;++index)
{
pthread_tpthread_id;
pthread_create(&pthread_id,NULL,do_session,NULL);
}
pthread_join();
return0;
}
void*do_accept(void*arg)
{
FCGX_Request*request=NULL;
while(1)
{
intrc=FCGX_Accept_r(request);
if(rc<0)
{
continue;
}
httpRequest.put(request);//httpRequest是一个生产者消费者模型,此处是放入任务
}
}
void*do_session(void*arg)
{
while(1)
{
FCGX_Request*request=NULL;
while(!request)
{
request=httpRequest.get();//此处是取出任务
}
stringstrRequestMethod;
stringstrGetData;
stringstrPostData;
intiPostDataLength;
if(FCGX_GetParam("QUERY_STRING",request->envp))
{
strGetData=FCGX_GetParam("QUERY_STRING",request->envp);
}
if(FCGX_GetParam("REQUEST_METHOD",request->envp))
{
iPostDataLength=::atoi(FCGX_GetParam("CONTENT_LENGTH",_pRequest->envp));
char*data=(char*)malloc(iPostDataLength+1);
::memset(data,0,iPostDataLength+1);
FCGX_GetStr(data,iPostDataLength,_pRequest->in);
strPostData=data;
free(data);
}
FCGX_PutS("Content-type:text/html\r\n\r\n",_pRequest->out);
FCGX_PutS(strPostData.c_str(),_pRequest->out);
}
}
工作到目前为止,只见了这三种模型,如果哪位好友有其他的模型,欢迎指点一下,在下不胜感激~~~
FCGX_Requestrequest; FCGX_Init(); intsock_fd=FCGX_OpenSocket("10.3.17.75:8003",100); FCGX_InitRequest(&request,sock_fd,0); while(FCGX_Accept_r(&request)>=0){ //getparam1 map<string,string>param_map; for(inti=0;request.envp[i];++i){ strings=request.envp[i]; size_tpos=s.find_first_of('='); if(pos>0&&pos<s.size()-1){ param_map.insert(make_pair(s.substr(0,pos),s.substr(pos+1))); } } //or2 char*clenstr=FCGX_GetParam("CONTENT_LENGTH",request.envp); //dosomething FCGX_Stream*fcgi_out=request.out; stringoutput="test"; FCGX_PutS(output.c_str(),fcgi_out); //finish FCGX_Finish_r(&request); }
由于最近工作的需要,本人学习了一下利用高性能webserver-Nginx,来发布C/C++编写的fastCGI程序,详细细节如下。
1.介绍
Nginx-高性能webserver,这个不用多说了,大家都知道。
FastCGI程序-常驻型CGI程序,它是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中并因此获得较高的性能。
Nginx要调用FastCGI程序,需要用到FastCGI进程管理程序(因为nginx不能直接执行外部的cgi程序,我们可使用lighttpd中的spawn-fastcgi来让nginx可支持外部cgi运行。也有其他方法安装nginx-fcgi来让nginx支持cgi,这里是使用spawn-fastcgi的方法),来达到调用FastCGI程序的目的。Nginx本身没有集成类似的模块,而Apache具备该功能模块,所以不需要额外安装FastCGI进程管理程序。
2.工作原理
Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket(这个socket可以是文件socket,也可以是ipsocket)。为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。
当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接收到请求,然后派生出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据发送给客户端。这就是Nginx+FastCGI的整个运作过程,如图1所示。
图1Nginx+FastCGI运行过程
FastCGI接口方式在脚本解析服务器(CGI应用程序服务器)上启动一个或者多个守护进程对动态脚本进行解析,这些进程就是FastCGI进程管理器,或者称为FastCGI引擎。spawn-fcgi与PHP-FPM都是FastCGI进程管理器(支持PHP和C/C++)。
介绍到这里,大家应该都对该模式有了一定的了解,下面开始进行实战!
3.环境部署
3.1.Nginx的安装、部署与配置
nginx下载目录
[安装]
下载以后解压并安装(请记得看README)
./configure(注意了类似checkingfor***...notfound项,可能是依赖包没有,则需要安装依赖包)
缺少pcre,则需要额外安装
缺少zlib,则需要额外安装
缺少OpenSSL,则需要额外安装
如果需要配置安装额外的功能模块,可以参考这里
make
makeinstall(默认安装到/usr/local/nginx)
[配置和管理]
1)执行选项
-c</path/to/config>为Nginx指定一个配置文件,来代替缺省的。不输入则使用默认的配置文件。
-t不运行,而仅仅测试配置文件。nginx将检查配置文件的语法的正确性,并尝试打开配置文件中所引用到的文件。
-v显示nginx的版本。
-V显示nginx的版本,编译器版本和配置参数。
2)检查配置文件
sudo./nginx-t
nginx:theconfigurationfile/usr/local/nginx/conf/nginx.confsyntaxisok
nginx:configurationfile/usr/local/nginx/conf/nginx.conftestissuccessful
3)启动-默认和特殊
/usr/local/nginx/sbin/nginx(默认启动方式)
/usr/local/nginx/sbin/nginx-c/usr/local/nginx/conf/nginx.conf(指定配置文件启动)
4)查看nginx进程号(master是主进程)
ps-ef|grepnginx
5)重新加载配置文件
sudokill-HUP[nginx主进程号]
通过系统的信号控制Nginx
可以使用信号系统来控制主进程。默认,nginx将其主进程的pid写入到/usr/local/nginx/logs/nginx.pid文件中。通过传递参数给./configure或使用pid指令,来改变该文件的位置。
主进程可以处理以下的信号:
命令
说明备注
TERM,INT快速关闭
QUIT从容关闭
HUP重载配置
用新的配置开始新的工作进程从容关闭旧的工作进程
USR1重新打开日志文件
USR2平滑升级可执行程序
WINCH从容关闭工作进程
6)默认目录结构
主目录:/usr/local/nginx/
配置目录:/usr/local/nginx/conf/
root目录:/usr/local/nginx/html/
可执行文件路径:/usr/local/nginx/sbin/
3.2.spawn_fastcgi的安装、部署与配置
spawn_fastcgi
下载以后解压并安装(请记得看README)
如果没有configure,请先执行./autogen.sh,生成configure
./configure
make
编译好以后,将可执行文件移动到nginx的sbin目录下
cp./src/spawn-fcgi/usr/local/nginx/sbin/(cp到nginx的安装目录下)
3.3.fastcgi库的安装(库绝对不是必须的,觉得技术好的大牛可以自己写)
库地址http://www.fastcgi.com/dist/fcgi.tar.gz
下载以后,解压并安装(默认安装)
./configure
make
makeinstall
4.Demo和web发布
4.1.Demo程序
[CGI程序]
[cpp]
#include<fcgi_stdio.h>
#include<stdlib.h>
intmain(){
intcount=0;
while(FCGI_Accept()>=0){
printf("Content-type:text/html\r\n"
"\r\n"
""
"FastCGIHello!"
"Requestnumber%drunningonhost%s"
"ProcessID:%d\n",++count,getenv("SERVER_NAME"),getpid());
}
return0;
}
[编译]
g++demo.cc-odemo-lfcgi
直接运行可执行文件,看看能否正常运行。如果出现缺少库libfcgi.so.0,则自己需要手动把/usr/local/lib/libfcgi.so.0库建立一个链接到/usr/lib/目录下:ln-s/usr/local/libfcgi.so.0/usr/lib/(或者把so的库路径添加到/etc/ld.so.conf,并执行ldconfig更新一下)
4.2.Web发布
1)将CGI可执行程序移动到nginx的安装目录下/usr/local/nginx/cgibin(文件夹不存在则自己创建)
2)启动spawn-fcgi管理进程,并绑定serverIP和端口(不要跟nginx的监听端口重合)
/usr/local/nginx/sbin/spawn-fcgi-a127.0.0.1-p8088-f/usr/local/nginx/cgibin/demo
查看一下9002端口是否已成功:netstat-na|grep8088
3)更改nginx.conf配置文件,让nginx转发请求
在http节点的子节点-"server节"点中下添加配置
location~\.cgi${
fastcgi_pass127.0.0.1:8088;
fastcgi_indexindex.cgi;
fastcgi_paramSCRIPT_FILENAMEfcgi$fastcgi_script_name;
includefastcgi_params;
}
4)重启nginx或者重新加载配置文件
重新加载配置文件
sudokill-HUP[pid]
或者
重启nginx
killallnginx
./nginx
5)打开浏览器访问一下吧
搞定收工,心里又小小的激动了一把!
allenrlin
2014/2/18
参考文献与资料
[1]Nginx+FastCGI运行原理
[4]什么是CGI、FastCGI、PHP-CGI、PHP-FPM、Spawn-FCGIhttp://www.mike.org.cn/articles/what-is-cgi-fastcgi-php-fpm-spawn-fcgi/
接着上篇《
1.CGI
1.1.环境变量
1.2.标准输入
2.FastCGI
3.nginxcgi/fastcgi
3.1.nginx+fastcgi
3.1.1.spawn-fcgi
3.1.2.编写fastcgi应用程序
3.1.3.nginxfastcgi配置
3.2.nginx+cgi
3.2.1fastcgi-wrapper
3.2.2.nginxfcgiwrap配置
3.2.3.编写cgi应用程序
参考链接
1.CGI
通用网关接口(CommonGatewayInterface/CGI)描述了客户端和服务器程序之间传输数据的一种标准,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。CGI独立于任何语言的,CGI程序可以用任何最初,CGI是在1993年由美国
lstep1.web服务器收到客户端(浏览器)的请求HttpRequest,启动CGI程序,并通过环境变量、标准输入传递数据
lstep2.cgi进程启动解析器、加载配置(如业务相关配置)、连接其它服务器(如数据库服务器)、逻辑处理等
lstep3.cgi程将处理结果通过标准输出、标准错误,传递给web服务器
lstep4.web服务器收到cgi返回的结果,构建HttpResponse返回给客户端,并杀死cgi进程
web服务器与cgi通过环境变量、标准输入、标准输出、标准错误互相传递数据。
1.1.环境变量
GET请求,它将数据打包放置在环境变量QUERY_STRING中,CGI从环境变量QUERY_STRING中获取数据。常见的环境变量如下表所示:环境变数 | 内容 |
AUTH_TYPE | 存取认证类型。 |
CONTENT_LENGTH | 由标准输入传递给CGI程序的数据长度,以bytes或字元数来计算。 |
CONTENT_TYPE | 请求的MIME类型。 |
GATEWAY_INTERFACE | 服务器的CGI版本编号。 |
HTTP_ACCEPT | 浏览器能直接接收的Content-types,可以有HTTPAcceptheader定义. |
HTTP_USER_AGENT | 递交表单的浏览器的名称、版本和其他平台性的附加信息。 |
HTTP_REFERER | 递交表单的文本的URL,不是所有的浏览器都发出这个信息,不要依赖它 |
PATH_INFO | 传递给cgi程式的路径信息。 |
QUERY_STRING | 传递给CGI程式的请求参数,也就是用"?"隔开,添加在URL后面的字串。 |
REMOTE_ADDR | client端的host名称。 |
REMOTE_HOST | client端的IP位址。 |
REMOTE_USER | client端送出来的使用者名称。 |
REMOTE_METHOD | client端发出请求的方法(如get、post)。 |
SCRIPT_NAME | CGI程式所在的虚拟路径,如/cgi-bin/echo。 |
SERVER_NAME | server的host名称或IP地址。 |
SERVER_PORT | 收到request的server端口。 |
SERVER_PROTOCOL | 所使用的通讯协定和版本编号。 |
SERVER_SOFTWARE | server程序的名称和版本。 |
1.2.标准输入
环境变量的大小是有一定的限制的,当需要传送的数据量大时,储存环境变量的空间可能会不足,造成数据接收不完全,甚至无法执行CGI程序。因此后来又发展出另外一种方法:POST,也就是利用I/O重新导向的技巧,让CGI程序可以由STDIN和STDOUT直接跟浏览器沟通。
当我们指定用这种方法传递请求的数据时,web服务器收到数据后会先放在一块输入缓冲区中,并且将数据的大小记录在CONTENT_LENGTH这个环境变数,然后调用CGI程式并将CGI程序的STDIN指向这块缓冲区,于是我们就可以很顺利的通过STDIN和环境变数CONTENT_LENGTH得到所有的资料,再没有资料大小的限制了。
总结:CGI使外部程序与Web服务器之间交互成为可能。CGI程式运行在独立的进程中,并对每个Web请求建立一个进程,这种方法非常容易实现,但效率很差,难以扩展。面对大量请求,进程的大量建立和消亡使操作系统性能大大下降。此外,由于地址空间无法共享,也限制了资源重用。
2.FastCGI
快速通用网关接口(FastCommonGatewayInterface/FastCGI)是当进来一个请求时,Web服务器把环境变量和这个页面请求通过一个unixdomainsocket(都位于同一物理服务器)或者一个IPSocket(FastCGI部署在其它物理服务器)传递给FastCGI进程。
lstep1.Web服务器启动时载入初始化FastCGI执行环境。例如IISISAPI、apachemod_fastcgi、nginxngx_http_fastcgi_module、lighttpdmod_fastcgi
lstep2.FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX域socket两种方式启动。
lstep3.当客户端请求到达Web服务器时,Web服务器将请求采用socket方式转发到FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器。Web服务器将CGI环境变量和标准输入发送到FastCGI子进程。
lstep4.FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web服务器。当FastCGI子进程关闭连接时,请求便处理完成。
lstep5.FastCGI子进程接着等待并处理来自Web服务器的下一个连接。
由于FastCGI程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的应用效率。它的速度效率最少要比CGI技术提高5倍以上。它还支持分布式的部署,即FastCGI程序可以在web服务器以外的主机上执行。
总结:CGI就是所谓的短生存期应用程序,FastCGI就是所谓的长生存期应用程序。FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute模式)。
3.nginxcgi/fastcgi
nginx不能像apache那样直接执行外部可执行程序,但nginx可以作为代理服务器,将请求转发给后端服务器,这也是nginx的主要作用之一。其中nginx就支持FastCGI代理,接收客户端的请求,然后将请求转发给后端fastcgi进程。下面介绍如何使用C/C++编写cgi/fastcgi,并部署到nginx中。3.1.nginx+fastcgi
通过前面的介绍知道,fastcgi进程由FastCGI进程管理器管理,而不是nginx。这样就需要一个FastCGI管理,管理我们编写fastcgi程序。本文使用spawn-fcgi作为FastCGI进程管理器。3.1.1.spawn-fcgi
spawn-fcgi是一个通用的FastCGI进程管理器,简单小巧,原先是属于lighttpd的一部分,后来由于使用比较广泛,所以就迁移出来作为独立项目了。spawn-fcgi使用pre-fork模型,功能主要是打开监听端口,绑定地址,然后fork-and-exec创建我们编写的fastcgi应用程序进程,退出完成工作。fastcgi应用程序初始化,然后进入死循环侦听socket的连接请求。安装spawn-fcgi:
l获取spawn-fcgi编译安装包,在
l解压缩spawn-fcgi-x.x.x.tar.gz包。
l进入解压缩目录,执行./configure。
lmake&makeinstall
如果遇到以下错误:“./autogen.sh:x:autoreconf:notfound”,因为没有安装automake工具,ubuntu用下面的命令安装好就可以了:sudoapt-getinstallautoconfautomakelibtool。
spawn-fcgi的帮助信息可以通过manspawn-fcgi或spawn-fcgi–h获得,下面是部分常用spawn-fcgi参数信息:
-f<fcgiapp>指定调用FastCGI的进程的执行程序位置 -a<addr>绑定到地址addr。 -p<port>绑定到端口port。 -s<path>绑定到unixdomainsocket -C<childs>指定产生的FastCGI的进程数,默认为5。(仅用于PHP) -P<path>指定产生的进程的PID文件路径。 -F<childs>指定产生的FastCGI的进程数(C的CGI用这个) -u和-gFastCGI使用什么身份(-u用户-g用户组)运行,CentOS下可以使用apache用户,其他的根据情况配置,如nobody、www-data等。 |
3.1.2.编写fastcgi应用程序
使用C/C++编写fastcgi应用程序,可以使用FastCGI软件开发套件或者其它开发框架,如fastcgi++。本文使用FastCGI软件开发套件——fcgi(
l获取fcgi编译安装包,在
l解压缩fcgi-x.x.x.tar.gz包。
l进入解压缩目录,执行./configure。
lmake&makeinstall
如果编译提示一下错误:
fcgio.cpp:Indestructor'virtualfcgi_streambuf::~fcgi_streambuf()':
fcgio.cpp:50:error:'EOF'wasnotdeclaredinthisscope
fcgio.cpp:Inmemberfunction'virtualintfcgi_streambuf::overflow(int)':
fcgio.cpp:70:error:'EOF'wasnotdeclaredinthisscope
fcgio.cpp:75:error:'EOF'wasnotdeclaredinthisscope
fcgio.cpp:Inmemberfunction'virtualintfcgi_streambuf::sync()':
fcgio.cpp:86:error:'EOF'wasnotdeclaredinthisscope
fcgio.cpp:87:error:'EOF'wasnotdeclaredinthisscope
fcgio.cpp:Inmemberfunction'virtualintfcgi_streambuf::underflow()':
fcgio.cpp:113:error:'EOF'wasnotdeclaredinthisscope
make[2]:***[fcgio.lo]Error1
make[2]:Leavingdirectory`/root/downloads/fcgi-2.4.1-SNAP-0910052249/libfcgi'
make[1]:***[all-recursive]Error1
make[1]:Leavingdirectory`/root/downloads/fcgi-2.4.1-SNAP-0910052249'
make:***[all]Error2
解决办法:在/include/fcgio.h文件中加上#include<cstdio>,然后再编译安装就通过了。
如果提示找不到动态库,请在LD_LIBRARY_PATH或/etc/ld.so.conf中添加fcgi的安装路径,如/usr/local/lib,并执行ldconfig更新一下。
#include"fcgi_stdio.h"
#include<stdlib.h>
intmain(void)
{
intcount=0;
while(FCGI_Accept()>=0)
printf("Content-type:text/html\r\n"
"\r\n"
"<title>FastCGIHello!</title>"
"<h1>FastCGIHello!</h1>"
"Requestnumber%drunningonhost<i>%s</i>\n",
++count,getenv("SERVER_NAME"));
return0;
}
编译g++main.cpp-odemo–lfcgi,并将demo部署到/opt/nginx-1.7.7/cgi-bin/目录
通过spawn-fcgi启动c/c++编写好的fastcgi程序:/opt/nginx-1.7.7/sbin/spawn-fcgi-a127.0.0.1-p8081-f/opt/nginx-1.7.7/cgi-bin/demo
3.1.3.nginxfastcgi配置
关于nginx的几个配置文件解析,可以参阅《这样nginx收到
3.2.nginx+cgi
nginx不能直接执行外部可执行程序,并且cgi是接收到请求时才会启动cgi进程,不像fastcgi会在一开就启动好,这样nginx天生是不支持cgi的。nginx虽然不支持cgi,但它支持fastCGI。所以,我们可以考虑使用fastcgi包装来支持cgi。原理大致如下图所示:pre-fork几个通用的代理fastcgi程序——fastcgi-wrapper,fastcgi-wrapper启动执行cgi然后将cgi的执行结果返回给nginx(fork-and-exec)。明白原理之后,编写一个fastcgi-warpper也比较简单。网上流传比较多的一个解决方案是,来自nginxwiki(
3.2.1.fastcgi-wrapper
其实编写C/C++的fastcgi-wrapper,就是写一个C/C++的fastcgi,步骤和原理跟前面的小节(nginx+fastcgi)一样。github上已经有人开源了,C写的fastcgi-wrapper:安装fcgiwrap:
l下载(
l解压缩fcgiwrap,进入解压目录
lautoreconf-i
l./configure
lmake&&makeinstall
启动fastcgi-wrapper:/opt/nginx-1.7.7/sbin/spawn-fcgi-f/usr/local/sbin/fcgiwrap-p8081
3.2.2.nginxfcgiwrap配置
在nginx.conf中增加下面的loaction配置块,这样所有的xxx.cgi请求都会走到fcgiwrap,然后fcgiwrap会执行cgi-bin目录下的cgi程序。3.2.3.编写cgi应用程序
#include<stdio.h>#include<stdlib.h>
intmain(void)
{
intcount=0;
printf("Content-type:text/html\r\n"
"\r\n"
"<title>CGIHello!</title>"
"<h1>CGIHello!</h1>"
"Requestnumber%drunningonhost<i>%s</i>\n",
++count,getenv("SERVER_NAME"));
return0;
}
tyler@ubuntu:~/ClionProjects/HelloFastCGI$g++cgi.cpp-ocgidemo-lfcgi
tyler@ubuntu:~/ClionProjects/HelloFastCGI$sudocpcgidemo/opt/nginx-1.7.7/cgi-bin/
注意图中的请求次数一直都是1,因为cgi的模式是fork-and-exec,每次都是一个新的进程。
参考链接
lCGI,lfastcgi,
lspawn-fcgi,
lfcgi,
lfcgiwrap,
很久之前看的spawn-fcgi的代码,当时因为需要改一下里面的环境变量。今天翻代码看到了就顺手记录一下,就当沉淀.备忘吧。
用spawn启动FCGI程序的方式为:./spawn-fcgi-a127.0.0.1-p9003-F${count}-f${webroot}/bin/demo.fcgi
这样就会启动count个demo.fcgi程序,他们共同监听同一个listen端口9003,从而提供服务。
spawn-fcgi代码不到600行,非常简短精炼,从main看起。其功能主要是打开监听端口,绑定地址,然后fork-exec创建FCGI进程,退出完成工作。
老方法,main函数使用getopt解析命令行参数,从而设置全局变量。如果设置了-P参数,需要保存Pid文件,就用open系统调用打开文件。之后根据是否是root用户启动,如果是root,得做相关的权限设置,比如chroot,chdir,setuid,setgid,setgroups等。
重要的是调用了bind_socket打开绑定本地监听地址,或者sock,再就是调用fcgi_spawn_connection创建FCGI进程,主要就是这2步。
[cpp]
intmain(intargc,char**argv)
{
if(!sockbeforechroot&&-1==(fcgi_fd=bind_socket(addr,port,unixsocket,sockuid,sockgid,sockmode)))
return-1;
/*droprootprivs*/
if(uid!=0)
{
setuid(uid);
}
else//非root用户启动,打开监听端口,进入listen模式。
{
if(-1==(fcgi_fd=bind_socket(addr,port,unixsocket,0,0,sockmode)))
return-1;
}
if(fcgi_dir&&-1==chdir(fcgi_dir))
{
fprintf(stderr,"spawn-fcgi:chdir('%s')failed:%s\n",fcgi_dir,strerror(errno));
return-1;
}
//fork创建FCGI的进程
returnfcgi_spawn_connection(fcgi_app,fcgi_app_argv,fcgi_fd,fork_count,child_count,pid_fd,nofork);
}
bind_socket函数用来创建套接字,绑定监听端口,进入listen模式。其参数unixsocket表明需要使用unixsock文件,这里不多介绍。函数代码页挺简单,莫过于通用的sock程序步骤:socket()->setsockopt()->bind()->listen();
[cpp]
staticintbind_socket(constchar*addr,unsignedshortport,constchar*unixsocket,uid_tuid,gid_tgid,intmode)
{
//bind_socket函数用来创建套接字,绑定监听端口,进入listen模式
if(-1==(fcgi_fd=socket(socket_type,SOCK_STREAM,0)))
{
fprintf(stderr,"spawn-fcgi:couldn'tcreatesocket:%s\n",strerror(errno));
return-1;
}
val=1;
if(setsockopt(fcgi_fd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))<0)
{
fprintf(stderr,"spawn-fcgi:couldn'tsetSO_REUSEADDR:%s\n",strerror(errno));
return-1;
}
if(-1==bind(fcgi_fd,fcgi_addr,servlen))
{
fprintf(stderr,"spawn-fcgi:bindfailed:%s\n",strerror(errno));
return-1;
}
if(unixsocket)
{
if(0!=uid||0!=gid)
{
if(0==uid)uid=-1;
if(0==gid)gid=-1;
if(-1==chown(unixsocket,uid,gid))
{
fprintf(stderr,"spawn-fcgi:couldn'tchownsocket:%s\n",strerror(errno));
close(fcgi_fd);
unlink(unixsocket);
return-1;
}
}
if(-1!=mode&&-1==chmod(unixsocket,mode))
{
fprintf(stderr,"spawn-fcgi:couldn'tchmodsocket:%s\n",strerror(errno));
close(fcgi_fd);
unlink(unixsocket);
return-1;
}
}
if(-1==listen(fcgi_fd,1024))
{
fprintf(stderr,"spawn-fcgi:listenfailed:%s\n",strerror(errno));
return-1;
}
returnfcgi_fd;
}
fcgi_spawn_connection函数的工作是循环一次次创建子进程,然后立即调用execv(appArgv[0],appArgv);替换可执行程序,也就试运行demo.fcgi。
[cpp]
staticintfcgi_spawn_connection(char*appPath,char**appArgv,intfcgi_fd,intfork_count,intchild_count,intpid_fd,
intnofork)
{
intstatus,rc=0;
structtimevaltv={0,100*1000};
pid_tchild;
while(fork_count-->0)
{
if(!nofork)//正常不会设置nofork的
{
child=fork();
}
else
{
child=0;
}
switch(child)
{
case0:
{
//子进程
charcgi_childs[64];
intmax_fd=0;
inti=0;
if(child_count>=0)
{
snprintf(cgi_childs,sizeof(cgi_childs),"PHP_FCGI_CHILDREN=%d",child_count);
putenv(cgi_childs);
}
//wuhaiwen:addchildidtothread
charbd_children_id[32];
snprintf(bd_children_id,sizeof(bd_children_id),"BD_CHILDREN_ID=%d",fork_count);
putenv(bd_children_id);
if(fcgi_fd!=FCGI_LISTENSOCK_FILENO)
{
close(FCGI_LISTENSOCK_FILENO);
dup2(fcgi_fd,FCGI_LISTENSOCK_FILENO);
close(fcgi_fd);
}
/*loosecontrolterminal*/
if(!nofork)
{
setsid();//执行setsid()之后,parent将重新获得一个新的会话session组id,child将仍持有原有的会话session组,
//这时parent退出之后,将不会影响到child了[luther.gliethttp].
max_fd=open("/dev/null",O_RDWR);
if(-1!=max_fd)
{
if(max_fd!=STDOUT_FILENO)dup2(max_fd,STDOUT_FILENO);
if(max_fd!=STDERR_FILENO)dup2(max_fd,STDERR_FILENO);
if(max_fd!=STDOUT_FILENO&&max_fd!=STDERR_FILENO)close(max_fd);
}
else
{
fprintf(stderr,"spawn-fcgi:couldn'topenandredirectstdout/stderrto'/dev/null':%s\n",strerror
(errno));
}
}
/*wedon'tneedtheclientsocket*/
for(i=3;i<max_fd;i++)
{
if(i!=FCGI_LISTENSOCK_FILENO)close(i);
}
/*forkandreplaceshell*/
if(appArgv)//如果有外的参数,就用execv执行,否则直接用shell执行
{
execv(appArgv[0],appArgv);
}
else
{
char*b=malloc((sizeof("exec")-1)+strlen(appPath)+1);
strcpy(b,"exec");
strcat(b,appPath);
/*execthecgi*/
execl("/bin/sh","sh","-c",b,(char*)NULL);
}
/*innoforkmodestderrisstillopen*/
fprintf(stderr,"spawn-fcgi:execfailed:%s\n",strerror(errno));
exit(errno);
break;
}
}
}
上面是创建子进程的部分代码,基本没啥可说明的。
对于子进程:注意一下dup2函数,由子进程运行,将监听句柄设置为标准输入,输出句柄。比如FCGI_LISTENSOCK_FILENO0号在FCGI里面代表标准输入句柄。函数还会关闭其他不必要的socket句柄。
然后调用execv替换可执行程序,运行新的二进制,也就是demo.fcgi的FCGI程序。这样子进程能够继承父进程的所有打开句柄,包括监听socket。这样所有子进程都能够在这个9002端口上进行监听新连接,谁拿到了谁就处理之。
对于父进程:主要需要用select等待一会,然后调用waitpid用WNOHANG参数获取一下子进程的状态而不等待子进程退出,如果失败就打印消息。否则将其PID写入文件。
[cpp]
default:
/*father*/
/*wait*/
select(0,NULL,NULL,NULL,&tv);
switch(waitpid(child,&status,WNOHANG))
{
case0:
fprintf(stdout,"spawn-fcgi:childspawnedsuccessfully:PID:%d\n",child);
/*writepidfile*/
if(pid_fd!=-1)
{
/*assumea32bitpid_t*/
charpidbuf[12];
snprintf(pidbuf,sizeof(pidbuf)-1,"%d",child);
write(pid_fd,pidbuf,strlen(pidbuf));
/*avoideolforthelastone*/
if(fork_count!=0)
{
write(pid_fd,"\n",1);
}
}
break;
基本就是上面的东西了,代码不多,但该有的都有,命令行解析,socket,fork,dup2等。很久之前看的在这里备忘一下。
cgi进程可以写成单线程的,也可以写成多线程的。
单线程就是main函数中有一个死循环,一直等待接受请求,有请求过来时,就处理请求,并返回结果,没有并发性。
多线程也分两种模式:一种是main函数起多个线程,每个线程都独立接受请求。另一种是main函数起一个accpet线程接受请求,多个do_session线程处理请求,这种模式需要一个任务队列的支持。
模式不同,采用的系统
单线程模式:
[cpp]
#include<fcgi_stdio.h>
#include<stdlib.h>
#include<string>
intmain()
{
while(FCGI_Accept()>=0)
{
stringstrGetData;
intiContentLength;
if(getenv("QUERY_STRING"))
{
strGetData=getenv("QUERY_STRING");
}
if(getenv("CONTENT_LENGTH"))
{
iContentLength=::atoi(getenv("CONTENT_LENGTH"));
}
char*data=(char*)::malloc(iContentLength+1);
::memset(data,0,iContentLength+1);
FCGI_fgets(data,iContentLength+1,FCGI_stdin);
FCGI_printf("Content-type:text/html\r\n\r\n");
FCGI_printf(data);
}
return0;
}
多线程模式,每个线程都独立接受请求:
[cpp]
#include<fcgi_stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string>
void*pthread_func(void*arg);
intmain()
{
intiThreadNum=10;
for(intindex=0;index!=iThreadNum;++index)
{
pthread_tpthread_id;
pthread_create(&pthread_id,NULL,pthread_func,NULL);
}
pthread_join();
return0;
}
void*pthread_func(void*arg)
{
FCGX_Request*request=NULL;
while(1)
{
intrc=FCGX_Accept_r(request);
if(rc<0)
{
continue;
}
stringstrRequestMethod;
stringstrGetData;
stringstrPostData;
intiPostDataLength;
if(FCGX_GetParam("QUERY_STRING",request->envp))
{
strGetData=FCGX_GetParam("QUERY_STRING",request->envp);
}
if(FCGX_GetParam("REQUEST_METHOD",request->envp))
{
iPostDataLength=::atoi(FCGX_GetParam("CONTENT_LENGTH",_pRequest->envp));
char*data=(char*)malloc(iPostDataLength+1);
::memset(data,0,iPostDataLength+1);
FCGX_GetStr(data,iPostDataLength,_pRequest->in);
strPostData=data;
free(data);
}
FCGX_PutS("Content-type:text/html\r\n\r\n",_pRequest->out);
FCGX_PutS(strPostData.c_str(),_pRequest->out);
}
}
多线程模式,一个accpet线程,多个do_session线程:
[cpp]
#include<fcgi_stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string>
void*do_accept(void*arg);
void*do_session(void*arg);
intmain()
{
pthread_tpthread_id;
pthread_create(&pthread_id,NULL,do_accept,NULL);
intiThreadNum=10;
for(intindex=0;index!=iThreadNum;++index)
{
pthread_tpthread_id;
pthread_create(&pthread_id,NULL,do_session,NULL);
}
pthread_join();
return0;
}
void*do_accept(void*arg)
{
FCGX_Request*request=NULL;
while(1)
{
intrc=FCGX_Accept_r(request);
if(rc<0)
{
continue;
}
httpRequest.put(request);//httpRequest是一个生产者消费者模型,此处是放入任务
}
}
void*do_session(void*arg)
{
while(1)
{
FCGX_Request*request=NULL;
while(!request)
{
request=httpRequest.get();//此处是取出任务
}
stringstrRequestMethod;
stringstrGetData;
stringstrPostData;
intiPostDataLength;
if(FCGX_GetParam("QUERY_STRING",request->envp))
{
strGetData=FCGX_GetParam("QUERY_STRING",request->envp);
}
if(FCGX_GetParam("REQUEST_METHOD",request->envp))
{
iPostDataLength=::atoi(FCGX_GetParam("CONTENT_LENGTH",_pRequest->envp));
char*data=(char*)malloc(iPostDataLength+1);
::memset(data,0,iPostDataLength+1);
FCGX_GetStr(data,iPostDataLength,_pRequest->in);
strPostData=data;
free(data);
}
FCGX_PutS("Content-type:text/html\r\n\r\n",_pRequest->out);
FCGX_PutS(strPostData.c_str(),_pRequest->out);
}
}
工作到目前为止,只见了这三种模型,如果哪位好友有其他的模型,欢迎指点一下,在下不胜感激~~~
相关文章推荐
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
- Nginx + FastCGI 程序(C/C++)搭建高性能web service的demo
- Nginx + FastCGI 程序(C/C++)搭建高性能web service的demo
- 搭建高性能Web服务器Nginx,10倍于Apache---Nginx+Php(FastCgi模式)
- 部署社区网站,搭建SVN、nginx、PHP,部署MFS并挂载,发布上线
- nginx+fastcgi+c/c++搭建高性能Web框架
- [转]nginx+fastcgi+c/c++搭建高性能Web框架
- nginx+fastcgi+c/c++搭建高性能Web框架