前阵子看nginx配置文件,发现有段location模块是如下所写:
location ~ [^/]\.php(/|$) {
try_files $uri = 404;
fastcgi_pass unix:/tmp/php-cgi.sock;
fastcgi_index index.php;
include fastcgi.conf;
}
当时就觉得很奇怪,因为在这之前看到的都是下面这样的:
location ~ .*\.php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi.conf;
}
这种好理解,nginx监听服务,当php类请求到来时,nginx充当代理,直接将服务转给9000端口,让php-cgi来处理。但是在上面配置中没选择转发端口,nginx是怎么找到php解释器的呢?查阅资料后得知,nginx是通过unix域套接字通知php解释器的,而且这种相对通过socket通信更高效。
unix域套接字仅仅复制数据,并不执行协议处理,不需要添加或者删除网络报头,也不需要计算校验和,更不需要产生顺序号,以及无需发送确认报文。就像是套接字和管道的混合,可以使用它们面向网络的域套接字接口或者使用socketpair()函数来创建一对无命名的、相互连接的unix域套接字。
#include <sys/socket.h>
/**
* domain在linux下通常取值AF_UNIX;
* type取值为SOCK_STREAM或者SOCK_DGRAM,表示在套接字上使用的是tcp还是udp;
* protocol值必须为0;
* sockfd[2]是一个含有两个元素的整型数组,实际上就是两个套接字描述符;
*
* 返回值若返回0,则成功;若出错,则返回-1
**/
int socketpair(int domain, int type, int protocol, int sockfd[2]);
当socketpair()方法执行成功时,sockfd[2]这两个套接字会具备如下关系:向sockfd[0]套接字写入数据,将可以从sockfd[1]套接字中读取到刚写入的数据;同样,向sockfd[1]套接字中写入数据,也可以从sockfd[0]中读取刚写入的数据。这样一对相互连接的unix域套接字就可以起到全双工管道的作用。
通常,在父子进程通信之前,会先调用socketpair()方法创建这样一组套接字,在调用fork()方法创建出子进程后,将会在父进程中关闭sockfd[1]套接字,仅使用sockfd[0]套接字向子进程发送数据以及接受从子进程发送来的数据;而在子进程中则关闭套接字sockfd[0],仅使用sockfd[1]接收父进程传来的数据,或者向父进程发送数据。
那么数据又是如何在这两个套接字之间传递的呢?这里实际上是在两个进程之间传递打开文件描述符。
技术上我们是将指向一个打开文件表项的指针从一个进程发送到另外一个进程,进程收到这个指针后,就将它存放在第一个可用的文件描述符中。这时两个进程就共享一个打开文件表,如上图所示。并且这与fork()之后的父进程和子进程共享打开文件表的情况是完全相同的。
为了用unix域套接字交换文件描述符,内核会调用sendmsg()和recvmsg()这两个函数。
#include <sys/socket.h>
//返回值:若成功,返回发送的字节数;若失败,返回-1
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
//返回值:若成功,返回接收数据的字节长度;若无可用数据或对方已结束,返回0;若失败,返回-1
ssize_t recvmsg(int sockfd, const struct msghdr *msg, int flags);
根据函数声明可以看到这两个函数的参数中都有一个指向msghdr结构的指针,该结构体就包含了所有关于要发送或要接收的数据信息。至于该结构体定义可参考套接字之msghdr结构,此处略。