說到 web 服務器想必大多數人首先想到的協議是 http,那么 http 之下則是 tcp,本篇文章將通過 tcp 來實作一個簡單的 web 服務器,
本篇文章將著重講解如何實作,對于 http 與 tcp 的概念本篇將不過多講解,
一、了解 Socket 及 web 服務作業原理
既然是基于 tcp 實作 web 服務器,很多學習 C 語言的小伙伴可能會很快的想到套接字 socket,socket 是一個較為抽象的通信行程,或者說是主機與主機進行資訊互動的一種抽象,socket 可以將資料流送入網路中,也可以接收資料流,
socket 的資訊互動與本地檔案資訊的讀取從表面特征上看類似,但其中所存在的撰寫復雜度是本地 IO 不能比擬的,但卻有相似點,
在 win 下 socket 的互動互動步驟為:
了解完了一個 socket 的基本步驟后我們了解一下一個基本 web 請求的用戶常規操作,操作分為:打開瀏覽器-->輸入資源地址 ip 地址-->得到資源,
當目標服務器接收到該操作產生掉請求后,我們可以把服務器的回應流程步驟看為:獲得 request 請求-->得到請求關鍵資料-->獲取關鍵資料-->發送關鍵資料,
服務器的這一步流程是在啟動socket 進行監聽后才能回應,通過監聽得知接收到請求,使用 recv 接收請求資料,從而根據該引數得到進行資源獲取,最后通過 send 將資料進行回傳,
二、創建sokect完成監聽
2.1 WSAStartup初始化
首先在c語言頭檔案中引入依賴 WinSock2.h:

在第一點中對 socket 的創建步驟已有說明,首先需要完成 socket 的初始化操作,使用函式 WSAStartup,該函式的原型為:

該函式的引數 wVersionRequired 表示 WinSock2 的版本號;
lpWSAData 引數為指向 WSADATA 的指標,WSADATA 結構用于 WSAStartup 初始化后回傳的資訊,
wVersionRequired 可以使用 MAKEWORD 生成,在這里可以使用版本 1.1 或版本2.2,1.1 只支持 TCP/IP,版本 2.1 則會有更多的支持,在此我們選擇版本 1.1,
首先宣告一個 WSADATA 結構體 :

隨后傳參至初始化函式 WSAStartup 完成初始化:

WSAStartup 若初始化失敗則會回傳非0值:

2.2 創建socket 套接字
初始化完畢后開始創建套接字,套接字創建使用函式,函式原型為:

在函式原型中,af 表示 IP 地址型別,使用 PF_INET 表示 IPV4,type 表示使用哪種通信型別,例如 SOCK_STREAM 表示 TCP,protocol 表示傳輸協議,使用 0 會根據前 2 個引數使用默認值,

創建完 socket 后,若為 -1 表示創建失敗,進行判斷如下:

2.3 系結服務器
創建完 socket 后需要對服務器進行系結,配置埠資訊、IP 地址等, 首先查看 bind 函式需要哪一些引數,函式原型如下:

引數 socket 表示系結的 socket,傳入 socket 即可;addr 為 sockaddr_in 的結構體變數的指標,在 sockaddr_in 結構體變數中配置一些服務器資訊;addrlen 為 addr 的大小值,
通過 bind 函式原型得知了我們所需要的資料,接下來創建一個 sockaddr_in 結構體變數用于配置服務器資訊:

隨后配置地址家族為AF_INET對應TCP/IP:

接著配置埠資訊:

再指定 ip 地址:

ip 地址若不確定可以手動輸入,最后使用神器 memset 初始化記憶體,完整代碼如下:

隨后使用 bind 函式進行系結且進行判斷是否系結成功:

2.4 listen進行監聽
系結成功后開始對埠進行監聽,查看 listen 函式原型:

函式原型中,引數 sockfd 表示監聽的套接字,backlog 為設定內核中的某一些處理(此處不進行深入講解),直接設定成 10 即可,最大上限為 128,使用監聽并且判斷是否成功代碼為:

此階段完整代碼如下:

運行代碼可得知代碼無錯誤,并且輸出 listening:

2.5 獲取請求
監聽完成后開始獲取請求,受限需要使用 accept 對套接字進行連接,accept 函式原型如下:

引數 sockfd 為指定的套接字;addr 為指向 struct sockaddr 的指標,一般為客戶端地址;addrlen 一般設定為設定為 sizeof(struct sockaddr_in) 即可,代碼為:

接下來開始接受客戶端的請求,使用recv函式,函式原型為:

引數 sockfd 為 accept 建立的通信;buf 為快取,資料存放的位置;len 為快取大小;flags 一般設定為0即可:

此時我們再到 accpt 和 recv 外層添加一個回圈,使之流程可重復:

并且可以在瀏覽器輸入 127.0.0.1:8080 將會看到客戶端列印了 listening 新建了鏈接:


我們添加printf陳述句可查看客戶端請求:


接下來我們對請求頭進行對應的操作,
2.6 請求處理層撰寫
得到請求后開始撰寫處理層,繼續接著代碼往下寫沒有層級,撰寫一個函式名為 req,該函式接收請求資訊與一個建立好的連接為引數:

然后先在 while 回圈中傳遞需要的值:

接著開始撰寫 req 函式,首先在 req 函式中標記當前目錄下:

隨后分離出請求與引數:

接著我們標記一些頭元素:

接著獲取請求引數,若獲取 index.html,就獲取當前路徑下的該檔案:

獲取檔案后表示請求 ok,我們先回傳一個 200 狀態:

接著撰寫一個發送函式 send_:

send 函式功能并不難在此不再贅述,就是一個遍歷發送的邏輯,隨后發送 http 回應與檔案型別:

隨后獲得請求檔案的描述,需要添加頭檔案#include <sys/stat.h>使用fstat,且向已連接的通信發生必要的資訊 :

最后發送資料:

最后訪問地址,得到當前目錄下 index.html 檔案資料,并且在瀏覽器渲染:

是不是很神奇呢,你也試試自己動手做出來一個簡單的 web 服務器吧!想要獲取完整代碼,關注我給我留言!

不管你是轉行也好,初學也罷,進階也可
——【值得關注進入】的C/C++編程學習進階俱樂部!——
涉及到:C語言、C++、windows編程、網路編程、QT界面開發、Linux編程、游戲編程、黑客等等......

一個活躍、高格調、高層次的程式員編程學習殿堂;編程入門只是順帶,思維的提高才有價值!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/228347.html
標籤:C
