用C寫乙個web伺服器(一) 基礎功能

2022-01-29 23:16:42 字數 3238 閱讀 6029

c 語言是一門很基礎的語言,程式設計師們對它推崇備至,雖然它是我的入門語言,但大學的 c 語言知道早已經還給了老師,c 的使用可以說是從頭學起。

之前一直在讀書,看了《c primer plus》、《apue》、《unp》,第一本看完之後雖然對 c 的語法有了大概的了解,可是要說應用,還差得很遠;後兩本算是咬著牙翻完的,應用更不敢說,只是對概念有了基本的認識。

我們都知道,學一門語言,只看不寫,很容易出現眼高手低,寫**無處下手的情況,於是終於在下班和週末擠出時間,準備寫乙個小專案。正好最近在看 nginx 伺服器與 php sapi 相關的知識,於是考慮以 nginx 的思想,寫乙個類似的簡化版 web 伺服器。

專案最終的成果不敢保證,像上次寫的 php 框架,在原理通透,技術要點掌握之後只剩下功能完善和**堆疊,也就沒有繼續下去的慾望了,於是太監了。。。 但是跟著學習和理解一遍一定會有很大收穫,這點是能保證的。 另外一直寫同一系列的東西會讓我有一種負擔感,而且偏底層的東西也需要很多時間去學習,這一系列可能會間隔更新,歡迎關注。

以 nginx 的思想來考慮本伺服器架構,初步考慮如下圖:

當然 php 程序也可以替換為其他的指令碼語言,可以更改原始碼中的 command 變數實現。

伺服器有乙個 master 程序,其有多個子程序為 worker 程序,master 程序受理客戶端的請求,然後分發給 worker 程序,worker 程序處理 http 頭資訊後將引數傳遞給 php 程序處理後,將結果返回到上層,再響應給客戶端。

也考慮過使用 php-fpm 的 worker 程序池方式,那樣的話 php-fpm 程序也要仿寫了,目前還不熟悉其內部構造,如果可以簡單化,自然向其靠攏。目前對 php 的 sapi 介面不熟,了解一下再考慮。

當前狀態的伺服器還極其簡單,總結下來有以下地方待優化:

雖然簡單,但伺服器已經有基本的功能了:

它監聽本地位址的 8080 埠,將接收到的 http 頭中的 path 資訊提出出來交給 php 程序,php 程序將引數資訊處理後返回給伺服器,伺服器拼裝 http 響應資訊再將結果返回給客戶端。

下面介紹各個功能的實現:

在介紹函式之間先用一張圖來介紹一次 http 請求中客戶端與伺服器之間的互動:

如圖:伺服器建立要進行:

呼叫 socket() 建立乙個連線;int socket(int domain, int type, int protocol);

呼叫 bind() 給套接字命名,繫結埠;int bind( int socket, const struct sockaddr *address, size_t address_len);

呼叫 listen() 監聽此套接字;int listen(int socket, int backlog);

呼叫 accept() 接受客戶端的連線;int accept(int socket, struct sockaddr *address, size_t *address_len);

呼叫 recv() 接收客戶端的資訊;int recv(int s, void *buf, int len, unsigned int flags);

呼叫 send() 將響應資訊傳送給客戶端;int send(int s, const void * msg, int len, unsigned int falgs);

socket 間的接收和傳送資訊在 c 中有幾個系列:write() / read() 、send() / recv() 、sendto() / recvfrom()、 sendmsg() / recvmsg(),可以自行選用。

另外函式引數釋義和要點,都被我注釋在**中了,感興趣的可以拉下來看一下,這些在網上也多有介紹,這裡不再贅述。

然後是 c 程序和 php 程序的互動,考慮到簡單易用,目前在 c 程序中直接執行 php 指令碼:

一開始使用 system() 函式:int system(const char *command);

system 函式會 fork 乙個子程序,在子程序中以 cli 方式執行 php 指令碼,並將錯誤碼或返回值返回。由於其結果型別不可控,編譯時會報乙個 warning。而且它將結果返回給父程序時,還會在標準輸出中列印結果,在伺服器執行時會丟擲異常。

於是找到了另乙個方法 popen,file * popen(const char * command, const char * type);

popen 同樣會 fork 乙個子程序來執行 command ,然後建立管道連到子程序的標準輸出裝置或標準輸入裝置,然後返回乙個檔案指標。隨後程序便可利用此檔案指標來讀取子程序的輸出裝置或是寫入到子程序的標準輸入裝置中。

其 type 引數便是控制連線到子程序的標準輸入還是標準輸出。我們想要子程序的標準輸出,於是傳入 type引數為 字元 「r」 (read)。同理,如果想寫入子程序標準輸入的話,可以傳值 「w」(write)。

另外在接收緩衝區內容的時候也出現了一點小意外:由於使用的 fgets() 方法會以換行符\n為一段的結尾,在接收 php 程序輸出時遇到換行會結束,這裡使用了乙個中間字串陣列line來接收每一行的資訊,將每一行的資訊拼裝到結果中。

**如下:

char * execphp(char *args)else;

}return buff;

}

socket 處於應用層和傳輸層之間的虛擬層,由於設定伺服器 socket 協議型別為 tcp,那麼 tcp 的握手揮手、資料讀取等步驟對於我們都是透明的。我們拿到的資料即 http 報文,關於 http 報文結構和其欄位解釋的文章非常多,這裡也不再多提。

首先使用 c 的 strtok() 方法,獲取到 http 頭的第一行,獲取到其 http 方法和 path 資訊,將這些資訊處理後,再使用 sprintf() 方法拼合 http 響應報文,主要替換了 響應內容長度和響應內容。

對 c 的用法還不太熟悉,沒用指標、結構等華麗操作,光簡單的實現就花了我好久。可能**路子也會有點野,希望有路過的大神能隨手提點一二;

支援一下我。部落格一直在更新,歡迎關注

用python寫乙個簡單的web伺服器

人生苦短,我用python 簡潔高效,這才是理想的語言啊 分享一點python的學習經驗 如何用python寫乙個簡單的web伺服器 首先,我們需要簡單地了解一下網路通訊協議,這裡用白話介紹一下tcp和udp這兩種傳輸層的協議 tcp 通訊過程之中每次通訊都會進行確認操作,確保報文的安全送達,相當於...

用C 寫乙個簡單的伺服器 Linux

下面是建立乙個簡單伺服器的基本流程,所用的埠是8099。後面貼了 一 基本流程 建立套接字 配置伺服器位址相關引數 將兩者繫結 監聽套接字上的埠 在上面建立的套接字上等待連線,並開啟乙個新的套接字用於與請求之間的互動 在傳送緩衝區中寫入響應 關閉連線 建立套接字 listenfd socket af...

寫乙個索引伺服器

今天把 jaxb 返回的 xml 物件 轉到了 lucene 的 document 然後仍給 index search 一把。還算順利搞定。接下來開始解決網路介面。寫乙個基於netty的索引伺服器。接收client仍過來的xml資料報。關於netty的資料除了 url 還有一部分中文的。url 大致...