前陣子看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結構,此處略。