標籤:ica absolute .class protobuf opera 行資料 編碼 ring symbol
前言
時隔一個多月,終於又有時間來更新我的伺服器了,這次更新主要實現一下 CGI 協議。
先放上GitHub連結 tinyServer-GitHub-枕邊書
作為一個伺服器,基本要求是能受理請求,提取資訊並將訊息分發給 CGI 解譯器,再將解譯器響應的訊息封裝後返回用戶端。在這個過程中,除了和用戶端 socket 之間的互動,還要牽扯到第三個實體 - 請求解譯器。
用戶端負責封裝請求和解析響應,伺服器的主要職責是管理串連、資料轉換、傳輸和分發用戶端請求,而真正進行資料文檔處理與資料庫操作的就是請求解譯器,這個解譯器,在 PHP 中一般是 PHP-FPM,JAVA 中是 Servlet。
我們之前進行的處理多在用戶端和伺服器之間的通訊,以及伺服器的內部調整,這次更新的內容主要是後面兩個實體之間的處理序間通訊。
處理序間通訊牽涉到三個方面,即方式和形式和內容。
方式指的是處理序間通訊的傳輸媒介,如 Nginx 中實現的 TCP 方式和 Unix Domain Socket,它們分別有跨機器和高效率的優點,還有我實現的伺服器用了很 low 的popen方式。
而形式就是資料格式了,我認為它並無定式,只要伺服器容易組織資料,解譯器能方便地接收並解析,最好也能節約傳輸資源,提高傳輸效率。目前的解決方案有經典的 xml,輕巧易理解的 json 和Google高效率的 protobuf。它們各有優點,我選擇了 json,主要是因為有CJson庫的存在,資料在 C 中方便組織,而在PHP中,一個json_decode()方法就完成了資料解析。
至於應該傳輸哪些內容呢?CGI 描述了一套協議:
CGI
通用閘道介面(Common Gateway Interface/CGI)是一種重要的互連網技術,可以讓一個用戶端,從網頁瀏覽器向執行在網路伺服器上的程式請求資料。CGI描述了伺服器和請求處理常式之間傳輸資料的一種標準。
CGI 是伺服器與解譯器互動的介面,伺服器負責受理請求,並將請求資訊解釋為一條條基本的請求資訊(在文檔中被稱為“中繼資料”),傳送給解譯器來解釋執行,而解譯器響應文檔和資料庫操作資訊。
之前看了一下 CGI 的 RFC 文檔,總結了幾個重要點,有興趣的可以看下底部參考文獻。常見規範(資訊太多,只考慮 MUST 的情況)如下:
CGI請求
- 伺服器根據 以
/ 分隔的直接選取解譯器;
- 如果有
AUTH 欄位,需要先執行 AUTH,再執行解譯器;
- 伺服器確認
CONTENT-LENGTH 表示的是資料解析出來的長度,如果附帶資訊體,則必須將長度欄位傳送到解譯器;
- 如果有
CONTENT-TYPE 欄位,伺服器必須將其傳給解譯器;若無此欄位,但有資訊體,則伺服器判斷此類型或拋棄資訊體;
- 伺服器必須設定
QUERY_STRING 欄位,如果用戶端沒有設定,服務端要傳一個Null 字元串“”
- 伺服器必須設定
REMOTE_ADDR,即用戶端請求IP;
REQUEST_METHOD 欄位必須設定, GET POST 等,大小寫敏感;
SCRIPT_NAME 表示執行的解譯器指令碼名,必須設定;
SERVER_NAME 和 SERVER_PORT 代表著大小寫敏感的伺服器名和伺服器受理時的TCP/IP連接埠;
SERVER_PROTOCOL 欄位指示著伺服器與解譯器協商的協議類型,不一定與用戶端請求的SCHEMA 相同,如‘https://‘可能為HTTP;
- 在
CONTENT-LENGTH 不為 NULL 時,伺服器要提供資訊體,此資訊體要嚴格與長度相符,即使有更多的可讀資訊也不能多傳;
- 伺服器必須將資料壓縮等編碼解析出來;
CGI響應
- CGI解譯器必須響應 至少一行頭 + 換行 + 響應內容;
- 解譯器在響應文檔時,必須要有
CONTENT-TYPE 頭;
- 在用戶端重新導向時,解譯器除了
client-redir-response=絕對url地址,不能再有其他返回,然後伺服器返回一個 302 狀態代碼;
- 解譯器響應 三位元字狀態代碼,具體配置可自行搜尋;
- 伺服器必須將所有解譯器返回的資料響應給用戶端,除非需要壓縮等編碼,伺服器不能修改響應資料;
Nginx和PHP的CGI實現
介紹完了 CGI,我們來參考一下當前伺服器 CGI 協議實現的成熟方案,這裡挑選我熟悉的 Nginx 和 PHP。
在 Nginx 和 PHP 的配合中,Nginx 自然是伺服器,而解譯器是 PHP 的 SAPI。
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。
PHP-FPM
快速通用閘道介面(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 選擇一個空閑背景工作處理序來處理請求。
糾偏
這裡總結一下幾個名字,以防混淆:
- sapi,是 PHP 與外部進程互動的介面;
- CGI/Fast-CGI(大寫)是一種協議;
- 本節中出現的 cgi(小寫),是指 PHP 的 sapi,即實現 CGI 協議的一種介面。
- php-fpm 是管理
實現了Fast-CGI協議的進程的一個進程。
代碼實現
介紹完了高端的Nginx伺服器,說一下我的實現:
伺服器解析 http 報文,實現 CGI 協議,將資料封裝成 json 格式,通過 PHP 的cli sapi 發送至 PHP 進程,PHP 進程解析後響應 json 格式資料,伺服器解析響應資料後封裝成 http 響應報文發送給用戶端。
http_parser
首要任務是解析 http 報文,C 中沒有很豐富字串函數,我也沒有封裝過常用的函數庫,所以只好臨時自己實現了一個util_http.c,這裡介紹幾個處理 http 報文時好用的字串函數。
strtok(char str[], const *delimeter),將 delimeter 設定為 "\n",分行處理 http 報文頭正好適合。
sscanf(const *str, format, dest1[,dest...]),它從字串中以特定格式讀取字串,讀取時的分隔字元是空格,用它來處理 http 請求行十分方便。
至於解析 http 報文頭的索引值對應,沒想到好方法,只好使用字元遍曆來判斷。
cJSON
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 開發的應該有各種函數庫吧,就算沒有自己的庫也要去找開源庫,自己造不了所有的輪子。
感覺伺服器又被自己寫殘了,留了很多業務類型的坑也不知道什麼時候會填,希望能有時間寫一個工業級的東西。。。
如果您覺得本文對您有協助,可以點擊下面的 推薦 支援一下我。部落格一直在更新,歡迎 關注 。
參考: The Common Gateway Interface (CGI) Version 1.1
深入理解PHP核心 ? 生命週期和Zend引擎
搞不清FastCgi與PHP-fpm之間是個什麼樣的關係
用C寫一個web伺服器(四) CGI協議