時隔一個多月,終於又有時間來更新個人服務器了,此次更新主要實現一下 CGI 協議。php
先放上GitHub連接 tinyServer-GitHub-枕邊書css
做爲一個服務器,基本要求是能受理請求,提取信息並將消息分發給 CGI 解釋器,再將解釋器響應的消息包裝後返回客戶端。在這個過程當中,除了和客戶端 socket 之間的交互,還要牽扯到第三個實體 - 請求解釋器。git
如上圖所示,客戶端負責封裝請求和解析響應,服務器的主要職責是管理鏈接、數據轉換、傳輸和分發客戶端請求,而真正進行數據文檔處理與數據庫操做的就是請求解釋器,這個解釋器,在 PHP 中通常是 PHP-FPM,JAVA 中是 Servlet。github
咱們以前進行的處理多在客戶端和服務器之間的通訊,以及服務器的內部調整,此次更新的內容主要是後面兩個實體之間的進程間通訊。web
進程間通訊牽涉到三個方面,即方式
和形式
和內容
。數據庫
方式指的是進程間通訊的傳輸媒介,如 Nginx 中實現的 TCP 方式和 Unix Domain Socket,它們分別有跨機器和高效率的優勢,還有我實現的服務器用了很 low 的popen
方式。apache
而形式就是數據格式了,我認爲它並沒有定式,只要服務器容易組織數據,解釋器能方便地接收並解析,最好也能節約傳輸資源,提升傳輸效率。目前的解決方案有經典的 xml,輕巧易理解的 json 和谷歌高效率的 protobuf。它們各有優勢,我選擇了 json,主要是由於有CJson庫的存在,數據在 C 中方便組織,而在PHP中,一個json_decode()
方法就完成了數據解析。編程
至於應該傳輸哪些內容呢?CGI 描述了一套協議:json
通用網關接口(Common Gateway Interface/CGI)是一種重要的互聯網技術,可讓一個客戶端,從網頁瀏覽器向執行在網絡服務器上的程序請求數據。CGI描述了服務器和請求處理程序之間傳輸數據的一種標準。segmentfault
CGI 是服務器與解釋器交互的接口,服務器負責受理請求,並將請求信息解釋爲一條條基本的請求信息(在文檔中被稱爲「元數據」),傳送給解釋器來解釋執行,而解釋器響應文檔和數據庫操做信息。
以前看了一下 CGI 的 RFC 文檔,總結了幾個重要點,有興趣的能夠看下底部參考文獻。常見規範(信息太多,只考慮 MUST 的狀況)以下:
/
分隔的路徑選擇解釋器;AUTH
字段,須要先執行 AUTH,再執行解釋器; CONTENT-LENGTH
表示的是數據解析出來的長度,若是附帶信息體,則必須將長度字段傳送到解釋器;CONTENT-TYPE
字段,服務器必須將其傳給解釋器;若無此字段,但有信息體,則服務器判斷此類型或拋棄信息體;QUERY_STRING
字段,若是客戶端沒有設置,服務端要傳一個空字符串「」REMOTE_ADDR
,即客戶端請求IP;REQUEST_METHOD
字段必須設置, GET POST 等,大小寫敏感;SCRIPT_NAME
表示執行的解釋器腳本名,必須設置;SERVER_NAME
和 SERVER_PORT
表明着大小寫敏感的服務器名和服務器受理時的TCP/IP端口;SERVER_PROTOCOL
字段指示着服務器與解釋器協商的協議類型,不必定與客戶端請求的SCHEMA 相同,如'https://'可能爲HTTP;CONTENT-LENGTH
不爲 NULL 時,服務器要提供信息體,此信息體要嚴格與長度相符,即便有更多的可讀信息也不能多傳;CONTENT-TYPE
頭;client-redir-response=絕對url地址
,不能再有其餘返回,而後服務器返回一個 302
狀態碼;介紹完了 CGI,咱們來參考一下當前服務器 CGI 協議實現的成熟方案,這裏挑選我熟悉的 Nginx 和 PHP。
在 Nginx 和 PHP 的配合中,Nginx 天然是服務器,而解釋器是 PHP 的 SAPI。
SAPI: Server abstraction API,指的是 PHP 具體應用的編程接口,它使得 PHP 能夠和其餘應用進行交互數據。
PHP 腳本要執行能夠經過不少種方式,經過 Web 服務器,或者直接在命令行下,也能夠嵌入在其餘程序中。常見的 sapi 有apache2handler、fpm-fcgi、cli、cgi-fcgi
,能夠經過 PHP 函數php_sapi_name()
來查看當前 PHP 執行所使用的 sapi。
PHP5.3 以前使用的與服務器交互的 sapi 是cgi
,它實現基本的 CGI 協議,因爲它每次處理請求都要建立一個進程、初始化進程、處理請求、銷燬進程,消耗過大,使得系統性能大大降低。
這時候便出現了 CGI 協議的升級版本 Fast-CGI。
快速通用網關接口(Fast Common Gateway Interface/FastCGI)是一種讓交互程序與Web服務器通訊的協議。FastCGI是早期通用網關接口(CGI)的加強版本。
Fast-CGI 提高效率主要靠將 CGI 解釋器長駐內存重現,避免了進程反覆加載的損耗。PHP 的 sapi cgi-fcgi
實現了 Fast-CGI 協議,提高了 PHP 處理 Web 請求的效率。
那麼咱們常見的 php-fpm 是什麼呢?它是一種進程管理器(PHP-FastCGI Process Manager),它負責管理實現 Fast-CGI 的那些進程(worker進程),它加載php.ini
信息,初始化 worker 進程,並實現平滑重啓和其餘高級功能。
Nginx 將請求都交給 php-fpm,fpm 選擇一個空閒工做進程來處理請求。
這裏總結一下幾個名字,以防混淆:
實現了Fast-CGI協議的進程
的一個進程。介紹完了高端的Nginx服務器,說一下個人實現:
服務器解析 http 報文,實現 CGI 協議,將數據包裝成 json 格式,經過 PHP 的cli
sapi 發送至 PHP 進程,PHP 進程解析後響應 json 格式數據,服務器解析響應數據後包裝成 http 響應報文發送給客戶端。
首要任務是解析 http 報文,C 中沒有很豐富字符串函數,我也沒有封裝過經常使用的函數庫,因此只好臨時本身實現了一個util_http.c
,這裏介紹幾個處理 http 報文時好用的字符串函數。
strtok(char str[], const *delimeter)
,將 delimeter 設置爲 "\n"
,分行處理 http 報文頭正好適合。
sscanf(const *str, format, dest1[,dest...])
,它從字符串中以特定格式讀取字符串,讀取時的分隔符是空格,用它來處理 http 請求行十分方便。
至於解析 http 報文頭的鍵值對應,沒想到好方法,只好使用字符遍從來判斷。
cJSON 是一個 C 實現的用以生成和解析 json 格式數據的函數庫,在 GitHub 上能夠輕鬆搜到,只用兩個文件 cJSON.c
和cJSON.h
便可。
須要注意:C 做爲強類型語言,往 json 內添加不一樣類型的數據要使用不一樣的方法,cJSON 支持 string, bool, number, cJSON object
等類型。
這裏簡單地介紹一下生成和解析的通常方法;
生成:
cJSON *root; // 聲明cJSON格式數據 root = cJSON_CreateObject(); // 建立一個cJSON對象 cJSON_AddStringToObject(root, "key", "value") // 往cJSON對象內添加鍵值對 char *output = cJSON_PrintUnformatted(root); // 生成json字符串 cJSON_Delete(root); // 別忘記釋放內存
解析:
cJSON *json = cJSON_Parse(response_json); value = cJSON_GetObjectItem(cJSON, "key");
固然,也能夠聲明 cJSON 類型的數據進行嵌套;
說實話,用最基本的 C 寫業務邏輯類的代碼真的能折磨死人,僅一個字符串的操做就能讓人慾仙欲死了。經常使用 C 開發的應該有各類函數庫吧,就算沒有本身的庫也要去找開源庫,本身造不了全部的輪子。
感受服務器又被本身寫殘了,留了不少業務類型的坑也不知道何時會填,但願能有時間寫一個工業級的東西。。。
若是您以爲本文對您有幫助,能夠點擊下面的 推薦 支持一下我。博客一直在更新,歡迎 關注 。