apache cgi过多 php_扯点儿高性能(一):CGI篇【搞附近】

首先得说明下CGI和高性能没有半毛钱关系,甚至是低性能的代名词。

其次是文中如果出现错误,微信公众号里发消息。

最后是我觉得我这个公众订阅号的名字起的不好,至少不如原来那个服务号的名字好。原来那个服务号的名字叫做【可能是东半球最高端的API社区】,他的精髓就在于最前面的【可能是】三个字上。现如今,我给新公众号起名的时候明显就是给自己挖坑了,给公众号本身以及本人都带来了极高的风险:【高性能API社区】。

199d267ed4d7d8188506fb91dc43aa99.png

目前看,主要矛盾在于公众号名称与公众号内容严重不符

我也被迫无奈不得不憋点儿关于“高性能”的专题系列。然而写文章真的是并不是一件很容易的事儿,我一直琢磨的问题有如下几条:

  • 怎么开始“高性能”这个话题

  • 怎么同时兼顾理论与广大和我一样的CV boys

  • 怎么同时不放弃原来的搞【附近】

  • 怎么将这个“高性能”持续下去,然后怎样顺利结束他

  • 怎么纠正掉自己爱吹牛立flag的恶习

然后我昨晚和张大彪、于巨蛀和刀爷吃烤羊腿的时候,灵感就像羊腿上散发的孜然味一样霎时间就穿透了我的内心,我突然就想明白如何解决?的几个问题了!

9452f822d14125db3eb2177ee136781a.png

下面的几条回答分别对应上面几条问题,你们感受下:

  • 既然有“高性能”,那想必之前一定有“低性能“,我们还是从历史上慢慢开始说起,先说低性能是怎么产生的,然后再慢慢铺高性能是怎么演变来的

  • 除了我复制粘贴别人的理论外,我也提供可复制粘贴的代码给别人

  • 代码就尽量向搞【附近】工程靠拢

  • 慢慢扯服务端随着历史发展出现的服务器模型、IO模型

  • 多吃羊腿多睡觉

至于工程实现语言选择,主要有两类组成:PHP和C。用PHP是为了从群众中来还要回到群众中去,用C则是为了体现出本文的历史沧桑感。

Let's ROCK


CGI是什么

CGI是一种协议,并不是一种具体的代码程序。上古时代的PHP程序就是靠CGI协议与HTTP服务器比如Apache协作完成。最开始那会儿Web站点的出现一般都是纯静态货色,只要你精通HTML和PS然后你就能配合Apache什么的就能搞出一个炫酷狂拽屌炸天的网站。

然而总有刁民想整幺蛾子:他们想整动态数据

但是这件事情让Apache来做总归是不合理的。从工程角度来讲,叫做耦合太严重;从UNIX哲学角度来讲,软件功能要精悍专一。Apache服务器就应该老老实实做好http,动态数据的读取应该交给其他程序来做。所以CGI就应运而生,全称叫做Common Gateway Interface。除了HTML和CSS以及jQuery外的任何一门语言都可以用来编写CGI程序,PHP、Python、Perl都可以的。


CGI粗暴流程

99ffe44e02c19239f9e0b99ba3b701b5.png

http服务器和cgi程序相互进行友好数据磋商一共就三个套路:

  • 环境变量

  • 标准输入

  • 标准输出

其中http服务器向cgi程序传输数据,是通过环境变量和标准输入。比如php里我们常见的$_SERVER['REQUEST_METHOD']等就是通过环境变量传递的,又或者说POST方法的PO过去的数据一般说来是通过标准输入向cgi写入。当cgi程序完成了CURD工作后处理好的数据需要返回给http服务器,此时则是通过cgi向标准输出中写数据完成。考虑到一般情况下http服务器的标准输入已经重定向到了cgi程序,所以cgi程序里直接echo、print_r等等就相当于直接将数据写入到了标准输出。

每当有HTTP请求打到http服务器上时候,服务器程序要做的标准流程就是fork出一个子进程,然后该子进程去exec写好的cgi程序。我听行业大佬们叫这个流程为fork-and-execute。毫无疑问,这就是传说中“低性能”代表操作。fork为宝贵系统资源,一次fork操作都是需要一些吃奶力气的,更可怕的时候如果有10000个http请求,就需要fork 10000次,你们感受下。

为了让我们广大泥腿子们内心找到灵魂归宿和熟悉的味道配方,为了更好的以熟悉的面孔向大家展示CGI协议的具体内容,我决定从ietf的CGI协议标准里给大家找一些老面孔混个脸熟,你们感受一下:

e2002b80bc9d60cf10c913d709cfd379.png

原来PHP里$_SERVER全局变量的数据都是来自于这里... ...


Ctrl + C && Ctrl + V

有道是老话说的好

c56d6b39ed79148c2abd7ef55ee57818.png

下面我们【粗暴地模拟】一下上古时代的基于CGI协议的Web开发是什么感受。首先我用上古语言C语言手写了一个【能用】的服务器,然后我们在服务器收到请求的时候fork一个子进程,在子进程中调用php-cgi程序(此处注意!php-cgi是fastcgi协议的实现)。我先把基于C语言的服务器代码贴一下,里面包含大量注释,一般人都能看懂,尽管该段程序可能充斥着内存泄漏、野指针到处飞、随时出现core dump,但是大概率情况下还是能用的。在后面的日子里,这坨烂代码将会伴随着我们逐渐演化为可能是【高性能】的服务器软件。

#include #include #include #include #include #include #include #include #define BUFFER_SIZE 4096extern char ** environ;int main( int argc, char * argv[] ) {  //for ( int index = 0; environ[ index ] != NULL; index++ ) {    //printf( "%s\n", environ[ index ] );  //}  if ( argc < 3 ) {    printf( "usage : ./server 0.0.0.0 6666\n" );    exit( -1 );  }  const char * ip_string_p = argv[ 1 ];  int port_int    = atoi( argv[ 2 ] );  int backlog_int = 10;  int common_ret_int;  int listen_socket_fd;  int client_socket_fd;  struct sockaddr_in socket_base_struct;  struct sockaddr_in client_base_struct;  int client_struct_length_int;  int socket_opt_address_reuse_int = 1;  listen_socket_fd = socket( PF_INET, SOCK_STREAM, 0 );  if ( listen_socket_fd < 0 ) {    exit( -1 );  }  setsockopt( listen_socket_fd, SOL_SOCKET, SO_REUSEADDR, &socket_opt_address_reuse_int, sizeof( socket_opt_address_reuse_int ) );  // 创建socket struct结构体并清空其中内存的数据  bzero( &socket_base_struct, sizeof( socket_base_struct ) );  socket_base_struct.sin_family = PF_INET;  // 将PORT转换成big-endian的PORT  socket_base_struct.sin_port   = htons( port_int );  // 将IP地址转换为big-endian的IP地址  inet_pton( PF_INET, ip_string_p, &socket_base_struct.sin_addr );  // 将分配好的address struct绑定好创建的listen socket上去  common_ret_int = bind( listen_socket_fd, ( struct sockaddr * )&socket_base_struct, sizeof( socket_base_struct ) );  if ( common_ret_int < 0 ) {    exit( -1 );  }  // 开始监听listen socket  common_ret_int = listen( listen_socket_fd, backlog_int );  if ( common_ret_int < 0 ) {    exit( -1 );  }  client_struct_length_int = sizeof( client_base_struct );  // 让服务器陷入无限循环中  while ( 1 ) {    client_socket_fd = accept( listen_socket_fd, ( struct sockaddr * )&client_base_struct, &client_struct_length_int );    if ( client_socket_fd < 0 ) {      exit( -1 );    }      // fork一下,子进程去调用处理 php-cgi 程序    pid_t pid;    pid = fork();    if ( 0 == pid ) {      // 别废话那么多,先能用再说      char buf[ BUFFER_SIZE ];      char content[ BUFFER_SIZE ];      char * http_state_line_string_p;      char * http_method_string_p;      char * http_query_string_p;      char * http_version_string_p;      FILE * file_fd;      recv( client_socket_fd, content, BUFFER_SIZE - 1, 0 );      /*       此处顺带为了让泥腿子们了解HTTP协议,我直接把http协议传输过来的数据       全部打印出来,你们感受一下传说中HTTP协议load的数据是长什么样子的.       一般说来,http服务器要做的就是解析这段http数据,解析成标准格式供我们       使用。       */      printf( "这就是传说中的HTTP协议的具体数据内容:\n" );      printf( "%s\n", content );      printf( "传说中HTTP协议数据内容已经OVER\n" );      /*       下面四行代码,是将HTTP数据中第一行:状态请求行 截取出来后开始解析       - GET则是PHP中常见的$_SERVER['http_method']       - /?username=xiaodushe则为QUERY_STRING       - HTTP/1.1则为http协议版本       这三项内容在php中都保存在了$_SERVER中..如果我没记错的话       strtok()是C语言函数中的一个奇葩......       */      http_state_line_string_p = strtok( content, "\r\n" );      http_method_string_p     = strtok( http_state_line_string_p, " " );      http_query_string_p      = strtok( NULL, " " );      http_version_string_p    = strtok( NULL, " " );      // 就先码死这个php-cgi程序吧,理论上cgi程序应该根据请求路径不同加载不同的cgi程序...      // 这个。。。先将就一下,码死成一个固定的cgi,能用就行..      // 我们将get参数通过设置环境变量传递给php-cgi程序      setenv( "QUERY_STRING", http_query_string_p, 1 );      setenv( "HTTP_METHOD", http_method_string_p, 1 );      setenv( "HTTP_VERSION", http_version_string_p, 1 );      // 从php-cgi拿回来数据...      FILE * fp = popen( "./test.php", "r" );      // 下面是按照http协议标准手工构造http数据返回给客户端      // 如果你不按照下面标准进行构造,客户端一般会返回一些提示,比如      // curl会返回:curl: (52) Empty reply from server      char html_entity[ BUFFER_SIZE ];      char html_body_content[ BUFFER_SIZE ];      fread( html_body_content, sizeof( char ), sizeof( html_body_content ), fp );      char html_response_template[] = "HTTP/1.1 200 OK\r\nContent-Type:text/plain\r\nContent-Length: %d\r\nHttp-Server:ti-server\r\n\r\n%s";      sprintf( html_entity, html_response_template, strlen( html_body_content ), html_body_content );      send( client_socket_fd, html_entity, sizeof( html_entity ), 0 );      // 关闭与客户端的连接.      close( client_socket_fd );      exit( -1 );    }  }  // 关闭socket  close( listen_socket_fd );  return 0;} 

上面的程序保存为server.c,请在Linux下输入如下命令编译一下,反正能用:

gcc server.c -o server  // 编译./server 0.0.0.0 6666   // 表示在6666端口上启动该服务器

与server.c同级目录下新建一个test.php文件,内容如下:

#! /usr/bin/php-cgi<?php echo 'http版本:'.$_SERVER['HTTP_VERSION'].PHP_EOL;echo 'http方法:'.$_SERVER['HTTP_METHOD'].PHP_EOL;echo 'query-string:'.$_SERVER['QUERY_STRING'].PHP_EOL;echo "hello,xiaodushe~".PHP_EOL; 

上述demo代码已经上传到github,地址为:

https://github.com/elarity/wechat-official-accounts-demo-code

好了,一切就绪,我们使用curl充当浏览器访问一下服务器。我这里服务器打印日志和curl客户端打印的日志分别如下图所示,你们感受一下:

服务器端的日志数据

9fe431153875de71b0838aa54aa21f03.png

curl客户端的日志数据

1575262f103006613af1f6da44627911.png

好了,这就是一个典型的极其粗暴的CGI程序流程。其中一些细节并不完全遵守CGI标准流程,重在参与重在参与。。。

遥想泥腿子之王Lerdorf当年,拖鞋裤衩,抠脚趾头间PHP CGI码网站...

说来有点儿意思,我当初刚接触PHP那会儿还是用的APACHE服务器,这APACHE最初和PHP就有两种友好的数据洽谈方式:

  • 本文中的CGI方式

  • 赫赫有名的PHP_MOD方式(模块方式)

  • FAST-CGI方式

由于PHP_MOD方式性能明显是要比CGI方式好不少的,所以默认情况下APACHE是用PHP_MOD方式。有兴趣的同学可以扒一个APACHE然后配置一波儿试试看,还是那句话:反正能用。


文章附录以及关键字:

  • CGI

  • CGI协议标准地址:https://tools.ietf.org/html/rfc3875

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值