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概念。