構建一個簡單的HTTP服務器
一著瓶、程序界面
啟動程序,運行后瓣履,在瀏覽器中輸入 http://localhost:8888/ 率翅,可返回結果。
二袖迎、基本原理
1冕臭、HTTP協議
- HTTP是超文本傳輸協議(HyperText Transfer Protocol)的簡稱,它建立在C/S架構的應用層協議燕锥。
- TCP/IP協議是協議層的內容辜贵,它定義了計算機間通信的基礎協議。
- HTTP归形、FTP托慨、Telnet等協議都是建立在TCP/IP協議基礎上的。
2连霉、請求/響應模型
- 在HTTP協議中榴芳,客戶端負責發(fā)起一個Request,該Request中含有請求方法跺撼、URL窟感、協議版本等信息,
- 服務端在接受到該Request后會返回一個Response歉井,該Response中含有狀態(tài)碼柿祈、響應內容等信息,
??這一模型稱為請求/響應模型。
3躏嚎、HTTP報文
??HTTP協議通信的核心是HTTP報文蜜自,根據報文發(fā)送者的不同,我們將其分為請求報文和響應報文卢佣。其中重荠,由客戶端發(fā)出的HTTP報文稱為請求報文,由服務端發(fā)出的報文稱為響應報文虚茶。
- 請求報文:請求報文通常由瀏覽器來發(fā)起戈鲁,當我們訪問一個網頁或者請求一個資源的時候都會產生請求報文。請求報文通常由HTTP請求行嘹叫、請求頭婆殿、消息體(可選)三部分組成,服務端在接收到請求報文后根據請求報文請求返回數據給客戶端罩扇,所以我們通常講的服務端開發(fā)實際上是指在服務端接收到信息以后處理的這個階段婆芦。
- 響應報文:響應報文是指在服務端接收并處理了客戶端的請求信息以后,服務端發(fā)送給客戶端的HTTP報文喂饥,服務端開發(fā)的重要工作就是處理來自客戶端的請求消约,所以這是我們開發(fā)一個HTTP服務器的核心工作。和請求報文類似仰泻,響應報文由HTTP狀態(tài)行荆陆、響應頭、消息體(可選)三部分組成集侯。例如我們通常熟悉的200和404分別表示連接正常和無法訪問資源這兩種響應狀態(tài)被啼。
4、基本請求方法
??HTTP協議的基本請求方法棠枉。常見的方法有GET浓体、POST、HEAD辈讶、DELETE命浴、OPTIONS、TRACE贱除、CONNECT
- GET:最為常見的一種請示方式生闲。當客戶端從服務器讀取文檔或者通過一個鏈接來訪問頁面的時候,都是采用GET方式來請求的月幌。GET請求的一個顯著標志是其請求參數附加在URL后碍讯,例如”/index.jsp?id=100&option=bind”這種形式即為GET方式請求。GET方式對用戶而言扯躺,傳遞參數過程是透明的捉兴,因為用戶可以通過瀏覽器地址欄直接看到參數蝎困,所以這種方式更適合用來設計API,即在不需要驗證身份或者對安全性要求不高的場合倍啥,需要注意的是GET方式請求對參數長度由一定限制禾乘。
- POST:POST克服了GET方式對參數長度存在限制的缺點,以鍵-值形式將參數封裝在HTTP請求中虽缕,所以從理論上講它對參數長度沒有限制(實際上會因為瀏覽器和操作系統的限制而大打折扣)始藕,而且對用戶來講參數傳遞過程是不可見的,所以它是一種相對安全的參數傳遞方式彼宠。通常用戶登錄都會采取這種方式鳄虱,我們在編寫爬蟲的時候遇到需要登錄的情況通常都需要使用POST方式進行模擬登錄。
5凭峡、思路
??HTTP是建立在TCP/IP協議上的,所以HTTP的協議應該考慮用TCP/IP協議的實現來實現决记,考慮到Socket是TCP/IP協議的一種實現摧冀,所以我們非常容易地想到應該用Socket來構建一個HTTP服務器。
??c#中的tcp通信實質就是Socket通信系宫。
??所以我們的思路是這樣的索昂,首先我們在服務端創(chuàng)建一個tcp通信來負責監(jiān)聽客戶端連接。每次客戶端發(fā)出請求后扩借,我們根據請問報文來判斷客戶端的請求類型椒惨,然后根據不同的請求類型進行相應的處理,這樣我們就設計了一個基本的HTTP服務器潮罪。
三康谆、程序解析
1、HttpServer類
??程序中我們創(chuàng)建了一個繼承于HttpServer的類嫉到,并實現了handleGETRequest 和handlePOSTRequest 這兩個抽象方法:
2沃暗、監(jiān)聽與接收
??這個WEB服務器由兩個組件構成,一個是負責啟動TcpListener來監(jiān)聽指定端口的HttpServer類何恶,并且用AcceptTcpClient()方法循環(huán)處理TCP連接請求孽锥,這是處理TCP連接的第一步。然后請求到達“已指定“的端口细层,接著就會創(chuàng)建一對新的端口惜辑,用來初始化客戶端到服務器端的TCP連接。這對端口便是TcpClient的session疫赎,這樣就可以保持我們的主端口可以繼續(xù)接收新的連接請求盛撑。從程序的代碼中我們可以看到,每一次監(jiān)聽程序都會創(chuàng)建一個新的TcpClien虚缎,HttpServer類又會創(chuàng)建一個新的HttpProcessor撵彻,然后啟動一個線程來操作钓株。HttpServer類中還包含兩個抽象方法,你必須實現這兩個方法陌僵。
3轴合、解析HTTP
??這樣,一個新的tcp連接就在自己的線程中被HttpProcessor處理了碗短,HttpProcessor的工作就是正確解析HTTP頭受葛,并且控制正確實現的抽象方法。
??HTTP請求由3部分組成偎谁,所以我們只需要用string.Split()方法將它們分割成3部分即可总滩,接下來就是接收和解析來自客戶端的HTTP頭信息,頭信息中的每一行數據是以Key-Value(鍵-值)形式保存巡雨,空行表示HTTP頭信息結束標志闰渔,我們代碼中用readHeaders方法來讀取HTTP頭信息。
4铐望、數據流的處理
到這里冈涧,我們已經了解了如何處理簡單的GET和POST請求,它們分別被分配給正確的handler處理程序正蛙。在本例中督弓,發(fā)送數據的時候有一個棘手的問題需要處理,那就是請求頭信息中包含發(fā)送數據的長度信息content-length乒验,當我們希望子類HttpServer中的handlePOSTRequest方法能夠正確處理數據時愚隧,我們需要將數據長度content-length信息一起放入數據流中,否則發(fā)送端會因為等待永遠不可能到達的數據和阻塞等待锻全。我們用了一種看起來不那么優(yōu)雅但非常有效的方法來處理這種情況狂塘,即將數據發(fā)送給POST處理方法前先把數據讀入到MemoryStream中。這種做法不太理想虱痕,原因如下:如果發(fā)送的數據很大睹耐,甚至是上傳一個文件,那么我們將這些數據緩存在內存就不那么合適甚至是不可能的部翘。理想的方法是限制post的長度硝训,比如我們可以將數據長度限制為10MB。
5新思、返回值與擴展
這個簡易版HTTP服務器另一個簡化的地方就是content-type的返回值窖梁,在HTTP協議中,服務器總是會將數據的MIME-Type發(fā)送給客戶端夹囚,告訴客戶端自己需要接收什么類型的數據纵刘。在writeSuccess()方法中,我們看到荸哟,服務器總是發(fā)送text/html類型假哎,如果你需要加入其他的類型瞬捕,你可以擴展這個方法。
四舵抹、C#中如何在一個類里訪問主窗體中的控件
1肪虎、增加一個靜態(tài)變量
public static Form1 frm1;
2、聲明一個form變量方便后面的調用
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
frm1 = this;
}
3惧蛹、在類中直接用Form類里的frm1變量去調用就可以了
Form1.frm1.WriteLog("get post data end", 1, Color.Green);
4扇救、修改控件的Modifiers屬性為public
- 最后如果你發(fā)現不成功的話,請記得將richtext的Modifiers屬性設置為public
五香嗓、參考資料
1迅腔、 PayneQin的博客https://blog.csdn.net/qinyuanpei/article/details/51757148
2、 烏班圖ysm的博客 https://blog.csdn.net/u012278016/article/details/104756543
3靠娱、 小y的博客 https://www.cnblogs.com/tuyile006/p/11857590.html
4沧烈、 百度經驗:https://jingyan.baidu.com/article/425e69e601c781be14fc1640.html
5、 秦元培的github https://github.com/qinyuanpei/HttpServer
六像云、程序下載
1掺出、dalong10的下載https://download.csdn.net/download/dalong10/12850855