PHP7系列:FastCGI协议详解(八)

URL请求的过程

在这里插入图片描述

CGI是什么

通用网关接口(Common Gateway Interface)是一种对接应用程序和网络服务器的接口协议,可以让一个客户端,从网页浏览器向服务器上的程序请求数据。CGI描述了服务器(如nginx)和请求处理程序(如PHP)之间传输数据的一种标准。

CGI程序运行在独立的进程中,并对每个Web请求创建一个进程,在结束时销毁。这种“每个请求一个新进程”的模型使得CGI程序非常容易实现,但效率很差,难以扩展。在高负载情况下,用于进程创建和销毁的操作系统开销变得很大。此外,由于地址空间无法共享,CGI进程模型限制了资源重用方法,如重用数据库连接、内存缓存等。

FastCGI

快速通用网关接口(Fast Common Gateway Interface)是一种让交互程序与Web服务器通信的协议。
因为CGI有效率差的缺点,FastCGI就是为了解决这个问题而生的。

与之前CIG为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI服务器管理,而不是web服务器。

当进来一个请求时,web服务器把环境变量和这个页面请求通过一个socket比如FastCGI进程与web服务器(都位于本地)或者一个TCP connection传递给FastCGI进程。

服务传入请求时,网络服务器通过Unix域套接字、命名管道或传输控制协议(TCP)连接向FastCGI进程发送环境变量信息和页面请求。响应通过相同的连接从进程返回到网络服务器,然后网络服务器将该响应传递给最终用户。连接可能在响应结束时关闭,但是web服务器和FastCGI服务进程都将持续,不会被销毁。
每个单独的FastCGI进程在其生命周期内可以处理许多请求,从而避免了每个请求进程创建和终止的开销。

#php\main\fastcgi.c 
static int fcgi_read_request(fcgi_request *req)
{
	fcgi_header hdr;
	int len, padding;
	unsigned char buf[FCGI_MAX_LENGTH+8];

	req->keep = 0;
	req->ended = 0;
	req->in_len = 0;
	req->out_hdr = NULL;
	req->out_pos = req->out_buf;

	if (req->has_env) {
		fcgi_hash_clean(&req->env);
	} else {
		req->has_env = 1;
	}

	if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) ||
	    hdr.version < FCGI_VERSION_1) {
		return 0;
	}

	len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;
	padding = hdr.paddingLength;

	while (hdr.type == FCGI_STDIN && len == 0) {
		if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) ||
		    hdr.version < FCGI_VERSION_1) {
			return 0;
		}

		len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;
		padding = hdr.paddingLength;
	}

	if (len + padding > FCGI_MAX_LENGTH) {
		return 0;
	}

	req->id = (hdr.requestIdB1 << 8) + hdr.requestIdB0;

	if (hdr.type == FCGI_BEGIN_REQUEST && len == sizeof(fcgi_begin_request)) {
		fcgi_begin_request *b;

		if (safe_read(req, buf, len+padding) != len+padding) {
			return 0;
		}

		b = (fcgi_begin_request*)buf;
		req->keep = (b->flags & FCGI_KEEP_CONN);
#ifdef TCP_NODELAY
		if (req->keep && req->tcp && !req->nodelay) {
# ifdef _WIN32
			BOOL on = 1;
# else
			int on = 1;
# endif

			setsockopt(req->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on));
			req->nodelay = 1;
		}
#endif
		switch ((b->roleB1 << 8) + b->roleB0) {
			case FCGI_RESPONDER:
				fcgi_hash_set(&req->env, FCGI_HASH_FUNC("FCGI_ROLE", sizeof("FCGI_ROLE")-1), "FCGI_ROLE", sizeof("FCGI_ROLE")-1, "RESPONDER", sizeof("RESPONDER")-1);
				break;
			case FCGI_AUTHORIZER:
				fcgi_hash_set(&req->env, FCGI_HASH_FUNC("FCGI_ROLE", sizeof("FCGI_ROLE")-1), "FCGI_ROLE", sizeof("FCGI_ROLE")-1, "AUTHORIZER", sizeof("AUTHORIZER")-1);
				break;
			case FCGI_FILTER:
				fcgi_hash_set(&req->env, FCGI_HASH_FUNC("FCGI_ROLE", sizeof("FCGI_ROLE")-1), "FCGI_ROLE", sizeof("FCGI_ROLE")-1, "FILTER", sizeof("FILTER")-1);
				break;
			default:
				return 0;
		}

		if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) ||
		    hdr.version < FCGI_VERSION_1) {
			return 0;
		}

		len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;
		padding = hdr.paddingLength;

		while (hdr.type == FCGI_PARAMS && len > 0) {
			if (len + padding > FCGI_MAX_LENGTH) {
				return 0;
			}

			if (safe_read(req, buf, len+padding) != len+padding) {
				req->keep = 0;
				return 0;
			}

			if (!fcgi_get_params(req, buf, buf+len)) {
				req->keep = 0;
				return 0;
			}

			if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) ||
			    hdr.version < FCGI_VERSION_1) {
				req->keep = 0;
				return 0;
			}
			len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;
			padding = hdr.paddingLength;
		}
	} else if (hdr.type == FCGI_GET_VALUES) {
		unsigned char *p = buf + sizeof(fcgi_header);
		zval *value;
		unsigned int zlen;
		fcgi_hash_bucket *q;

		if (safe_read(req, buf, len+padding) != len+padding) {
			req->keep = 0;
			return 0;
		}

		if (!fcgi_get_params(req, buf, buf+len)) {
			req->keep = 0;
			return 0;
		}

		q = req->env.list;
		while (q != NULL) {
			if ((value = zend_hash_str_find(&fcgi_mgmt_vars, q->var, q->var_len)) == NULL) {
				q = q->list_next;
				continue;
			}
			zlen = (unsigned int)Z_STRLEN_P(value);
			if ((p + 4 + 4 + q->var_len + zlen) >= (buf + sizeof(buf))) {
				break;
			}
			if (q->var_len < 0x80) {
				*p++ = q->var_len;
			} else {
				*p++ = ((q->var_len >> 24) & 0xff) | 0x80;
				*p++ = (q->var_len >> 16) & 0xff;
				*p++ = (q->var_len >> 8) & 0xff;
				*p++ = q->var_len & 0xff;
			}
			if (zlen < 0x80) {
				*p++ = zlen;
			} else {
				*p++ = ((zlen >> 24) & 0xff) | 0x80;
				*p++ = (zlen >> 16) & 0xff;
				*p++ = (zlen >> 8) & 0xff;
				*p++ = zlen & 0xff;
			}
			memcpy(p, q->var, q->var_len);
			p += q->var_len;
			memcpy(p, Z_STRVAL_P(value), zlen);
			p += zlen;
			q = q->list_next;
		}
		len = (int)(p - buf - sizeof(fcgi_header));
		len += fcgi_make_header((fcgi_header*)buf, FCGI_GET_VALUES_RESULT, 0, len);
		if (safe_write(req, buf, sizeof(fcgi_header) + len) != (ssize_t)sizeof(fcgi_header)+len) {
			req->keep = 0;
			return 0;
		}
		return 0;
	} else {
		return 0;
	}

	return 1;
}

FPM

FPM或者说是PHP-FPM,就是Fastcgi Process Manager(Fastcgi 进程管理器)。在说FPM之前就不得不先说一下,PHP官方推出的php-cgi.php-cgi虽然是官方出品,但是当修改php.ini文件后,需要重启php-cgi,而且php-cgi不支持平滑重启。PHP-FPM是非官方出品的,是从php5.3之后加入的,它是对FastCGI协议的具体实现,负责一个进程池来管理web服务器的请求。

fpm的实现就是创建一个master进程,在master进程中创建并监听socket,然后fork出多个子进程,这些子进程各自accept请求,子进程的处理非常简单,它在启动后阻塞在accept上,有请求到达后开始读取请求数据,读取完成后开始处理然后再返回,在这期间是不会接收其它请求的,也就是说fpm的子进程同时只能响应一个请求,只有把这个请求处理完成后才会accept下一个请求,这一点与nginx的事件驱动有很大的区别,nginx的子进程通过epoll管理套接字,如果一个请求数据还未发送完成则会处理下一个请求,即一个进程会同时连接多个请求,它是非阻塞的模型,只处理活跃的套接字。

fpm的master进程与worker进程之间不会直接进行通信,master通过共享内存获取worker进程的信息,比如worker进程当前状态、已处理请求数等,当master进程要杀掉一个worker进程时则通过发送信号的方式通知worker进程。

fpm可以同时监听多个端口,每个端口对应一个worker pool,而每个pool下对应多个worker进程,类似nginx中server概念。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值