如何用C寫一個web服務器之CGI協(xié)議
前言
這次更新主要實現(xiàn)一下 CGI 協(xié)議。
先放上GitHub鏈接https://github.com/zhenbianshu/tinyServer
作為一個服務器,基本要求是能受理請求,提取信息并將消息分發(fā)給 CGI 解釋器,再將解釋器響應的消息包裝后返回客戶端。在這個過程中,除了和客戶端 socket 之間的交互,還要牽扯到第三個實體 - 請求解釋器。

如上圖所示,客戶端負責封裝請求和解析響應,服務器的主要職責是管理連接、數(shù)據(jù)轉換、傳輸和分發(fā)客戶端請求,而真正進行數(shù)據(jù)文檔處理與數(shù)據(jù)庫操作的就是請求解釋器,這個解釋器,在 PHP 中一般是 PHP-FPM,JAVA 中是 Servlet。
我們之前進行的處理多在客戶端和服務器之間的通信,以及服務器的內部調整,這次更新的內容主要是后面兩個實體之間的進程間通信。
進程間通信牽涉到三個方面,即方式和形式和內容。
方式指的是進程間通信的傳輸媒介,如 Nginx 中實現(xiàn)的 TCP 方式和 Unix Domain Socket,它們分別有跨機器和高效率的優(yōu)點,還有我實現(xiàn)的服務器用了很 low 的popen方式。
而形式就是數(shù)據(jù)格式了,我認為它并無定式,只要服務器容易組織數(shù)據(jù),解釋器能方便地接收并解析,最好也能節(jié)約傳輸資源,提高傳輸效率。目前的解決方案有經(jīng)典的 xml,輕巧易理解的 json 和谷歌高效率的 protobuf。它們各有優(yōu)點,我選擇了 json,主要是因為有CJson庫的存在,數(shù)據(jù)在 C 中方便組織,而在PHP中,一個json_decode()方法就完成了數(shù)據(jù)解析。
至于應該傳輸哪些內容呢?CGI 描述了一套協(xié)議:
CGI
通用網(wǎng)關接口(Common Gateway Interface/CGI)是一種重要的互聯(lián)網(wǎng)技術,可以讓一個客戶端,從網(wǎng)頁瀏覽器向執(zhí)行在網(wǎng)絡服務器上的程序請求數(shù)據(jù)。CGI描述了服務器和請求處理程序之間傳輸數(shù)據(jù)的一種標準。
CGI 是服務器與解釋器交互的接口,服務器負責受理請求,并將請求信息解釋為一條條基本的請求信息(在文檔中被稱為“元數(shù)據(jù)”),傳送給解釋器來解釋執(zhí)行,而解釋器響應文檔和數(shù)據(jù)庫操作信息。
之前看了一下 CGI 的 RFC 文檔,總結了幾個重要點,有興趣的可以看下底部參考文獻。常見規(guī)范(信息太多,只考慮 MUST 的情況)如下:
CGI請求
- 服務器根據(jù) 以 / 分隔的路徑選擇解釋器;
- 如果有 AUTH 字段,需要先執(zhí)行 AUTH,再執(zhí)行解釋器;
- 服務器確認 CONTENT-LENGTH 表示的是數(shù)據(jù)解析出來的長度,如果附帶信息體,則必須將長度字段傳送到解釋器;
- 如果有 CONTENT-TYPE 字段,服務器必須將其傳給解釋器;若無此字段,但有信息體,則服務器判斷此類型或拋棄信息體;
- 服務器必須設置 QUERY_STRING 字段,如果客戶端沒有設置,服務端要傳一個空字符串“”
- 服務器必須設置 REMOTE_ADDR,即客戶端請求IP;
- REQUEST_METHOD 字段必須設置, GET POST 等,大小寫敏感;
- SCRIPT_NAME 表示執(zhí)行的解釋器腳本名,必須設置;
- SERVER_NAME 和 SERVER_PORT 代表著大小寫敏感的服務器名和服務器受理時的TCP/IP端口;
- SERVER_PROTOCOL 字段指示著服務器與解釋器協(xié)商的協(xié)議類型,不一定與客戶端請求的SCHEMA 相同,如'https://'可能為HTTP;
- 在 CONTENT-LENGTH 不為 NULL 時,服務器要提供信息體,此信息體要嚴格與長度相符,即使有更多的可讀信息也不能多傳;
- 服務器必須將數(shù)據(jù)壓縮等編碼解析出來;
CGI響應
- CGI解釋器必須響應 至少一行頭 + 換行 + 響應內容;
- 解釋器在響應文檔時,必須要有 CONTENT-TYPE 頭;
- 在客戶端重定向時,解釋器除了 client-redir-response=絕對url地址,不能再有其他返回,然后服務器返回一個 302 狀態(tài)碼;
- 解釋器響應 三位數(shù)字狀態(tài)碼,具體配置可自行搜索;
- 服務器必須將所有解釋器返回的數(shù)據(jù)響應給客戶端,除非需要壓縮等編碼,服務器不能修改響應數(shù)據(jù);
Nginx和PHP的CGI實現(xiàn)
介紹完了 CGI,我們來參考一下當前服務器 CGI 協(xié)議實現(xiàn)的成熟方案,這里挑選我熟悉的 Nginx 和 PHP。
在 Nginx 和 PHP 的配合中,Nginx 自然是服務器,而解釋器是 PHP 的 SAPI。
SAPI
SAPI: Server abstraction API,指的是 PHP 具體應用的編程接口,它使得 PHP 可以和其他應用進行交互數(shù)據(jù)。
PHP 腳本要執(zhí)行可以通過很多種方式,通過 Web 服務器,或者直接在命令行下,也可以嵌入在其他程序中。常見的 sapi 有apache2handler、fpm-fcgi、cli、cgi-fcgi,可以通過 PHP 函數(shù)php_sapi_name()來查看當前 PHP 執(zhí)行所使用的 sapi。
PHP5.3 之前使用的與服務器交互的 sapi 是cgi,它實現(xiàn)基本的 CGI 協(xié)議,由于它每次處理請求都要創(chuàng)建一個進程、初始化進程、處理請求、銷毀進程,消耗過大,使得系統(tǒng)性能大大下降。
這時候便出現(xiàn)了 CGI 協(xié)議的升級版本 Fast-CGI。
PHP-FPM
快速通用網(wǎng)關接口(Fast Common Gateway Interface/FastCGI)是一種讓交互程序與Web服務器通信的協(xié)議。FastCGI是早期通用網(wǎng)關接口(CGI)的增強版本。
Fast-CGI 提升效率主要靠將 CGI 解釋器長駐內存重現(xiàn),避免了進程反復加載的損耗。PHP 的 sapi cgi-fcgi實現(xiàn)了 Fast-CGI 協(xié)議,提升了 PHP 處理 Web 請求的效率。
那么我們常見的 php-fpm 是什么呢?它是一種進程管理器(PHP-FastCGI Process Manager),它負責管理實現(xiàn) Fast-CGI 的那些進程(worker進程),它加載php.ini信息,初始化 worker 進程,并實現(xiàn)平滑重啟和其他高級功能。
Nginx 將請求都交給 php-fpm,fpm 選擇一個空閑工作進程來處理請求。
糾偏
這里總結一下幾個名字,以防混淆:
- sapi,是 PHP 與外部進程交互的接口;
- CGI/Fast-CGI(大寫)是一種協(xié)議;
- 本節(jié)中出現(xiàn)的 cgi(小寫),是指 PHP 的 sapi,即實現(xiàn) CGI 協(xié)議的一種接口。
- php-fpm 是管理實現(xiàn)了Fast-CGI協(xié)議的進程的一個進程。
代碼實現(xiàn)
介紹完了高端的Nginx服務器,說一下我的實現(xiàn):
服務器解析 http 報文,實現(xiàn) CGI 協(xié)議,將數(shù)據(jù)包裝成 json 格式,通過 PHP 的cli sapi 發(fā)送至 PHP 進程,PHP 進程解析后響應 json 格式數(shù)據(jù),服務器解析響應數(shù)據(jù)后包裝成 http 響應報文發(fā)送給客戶端。
http_parser
首要任務是解析 http 報文,C 中沒有很豐富字符串函數(shù),我也沒有封裝過常用的函數(shù)庫,所以只好臨時自己實現(xiàn)了一個util_http.c,這里介紹幾個處理 http 報文時好用的字符串函數(shù)。
strtok(char str[], const *delimeter),將 delimeter 設置為 "\n",分行處理 http 報文頭正好適合。
sscanf(const *str, format, dest1[,dest...]),它從字符串中以特定格式讀取字符串,讀取時的分隔符是空格,用它來處理 http 請求行十分方便。
至于解析 http 報文頭的鍵值對應,沒想到好方法,只好使用字符遍歷來判斷。
cJSON
cJSON 是一個 C 實現(xiàn)的用以生成和解析 json 格式數(shù)據(jù)的函數(shù)庫,在 GitHub 上可以輕松搜到,只用兩個文件 cJSON.c和cJSON.h即可。
需要注意:C 作為強類型語言,往 json 內添加不同類型的數(shù)據(jù)要使用不同的方法,cJSON 支持 string, bool, number, cJSON object等類型。
這里簡單地介紹一下生成和解析的一般方法;
生成:
cJSON *root; // 聲明cJSON格式數(shù)據(jù) root = cJSON_CreateObject(); // 創(chuàng)建一個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 類型的數(shù)據(jù)進行嵌套;
以上就是如何用C寫一個web服務器之CGI協(xié)議的詳細內容,更多關于用C寫一個web服務器之CGI協(xié)議的資料請關注腳本之家其它相關文章!
相關文章
Qt中關聯(lián)容器QMap,QMultiMap,QHash,QMultiHash的使用
本文主要介紹了Qt中關聯(lián)容器QMap,QMultiMap,QHash,QMultiHash的使用,這些關聯(lián)容器在Qt中提供了靈活而強大的數(shù)據(jù)結構選項,根據(jù)具體的需求和使用場景,您可以選擇適合的容器來存儲和管理數(shù)據(jù),感興趣的可以了解一下2023-09-09
C++利用類實現(xiàn)矩陣的數(shù)乘,乘法以及點乘
這篇文章主要為大家詳細介紹了C++如何利用類實現(xiàn)矩陣的數(shù)乘,乘法以及點乘,文中的示例代碼講解詳細,對我們學習C++有一定幫助,需要的可以參考一下2022-11-11
C++?容器中map和unordered?map區(qū)別詳解
這篇文章主要為大家介紹了C++?容器中map和unordered?map區(qū)別示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
C語言 模擬實現(xiàn)strcpy與strcat函數(shù)詳解
這篇文章主要介紹了怎樣用C語言模擬實現(xiàn)strcpy與strcat函數(shù),strcpy()函數(shù)是C語言中的一個復制字符串的庫函數(shù),strcat()函數(shù)的功能是實現(xiàn)字符串的拼接2022-04-04

