前言
昨天嘗試了,基于對http協(xié)議的探究,我們用控制臺寫了一個簡單的瀏覽器。盡管瀏覽器很low,但是對于http協(xié)議有個更好的理解。
說了上面這一段,諸位猜到我要干嘛了嗎?(其實不用猜哈,標(biāo)題里都有,又都不瞎。。。我就是調(diào)侃一下,說些沒營養(yǎng)的笑話。我認(rèn)為這樣能不那么枯燥,盡管不好笑吧,但這不重要?。?/span>
沒錯,今天要嘗試的東西,是自己寫一個web服務(wù)器。初衷依舊和昨天一樣,旨在理解一些東西,而不是真的寫出一個多牛的東西。
第一次嘗試(V1.0)
1.理論支持
其實關(guān)于http協(xié)議的理論方面我在《寫一個瀏覽器》的博文中已經(jīng)說過了,這里不再累述了。
這里主要要說的關(guān)于Socket方面的。主要是一個例子,關(guān)于Socket如何建立服務(wù)端程序的簡單的代碼掩飾。
static void Main(string[] args)
{
//創(chuàng)建一個新的Socket,這里我們使用最常用的基于TCP的Stream Socket(流式套接字)
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//將該socket綁定到主機(jī)上面的某個端口
socket.Bind(new IPEndPoint(IPAddress.Any, 4530));
//啟動監(jiān)聽,并且設(shè)置一個最大的隊列長度
socket.Listen(4);
//到這里我們的Socket已經(jīng)運(yùn)行起來了,但僅僅是運(yùn)行起來,什么都不會做的!
Console.WriteLine("Server is ready!");
Console.Read();
}
打開調(diào)試一口,因為要監(jiān)聽某個端口,windows會有這樣的一個提示。點允許就好了。
![clipboard[1] clipboard[1]](http://image83.360doc.com/DownloadImg/2015/03/1708/51254062_3.png)
從上面例子看,socket的職責(zé)僅僅是監(jiān)聽4530端口,什么都不會做的!
就像一個人的耳朵。他會聆聽,但是不會傾訴。職責(zé)所限,我們需要一個監(jiān)聽4530端口的耳朵。
但是從交流的角度看,web服務(wù)器僅僅能聆聽是不夠的。
請求來了以后(監(jiān)聽到請求以后),我還需要一個既能聆聽,又能訴說的Socket。去和請求交流。
剛剛那個socket為啥不能直接交流呢? 不不不,他得繼續(xù)去聆聽新的請求。
2.說說思路
這次實驗的主要思路是這樣的。
1)監(jiān)聽4530端口
2)當(dāng)請求來了以后,我們使用Socket socket = serverSocket.Accept();建立一個新的socket。
3)新的socket返回一個字符串給請求方!
完了(讀liao)。
也就是說,我們v1.0版本的web服務(wù)器,不管你如何請求,他都會返回你同一個字符串!(任性吧?其實我挺喜歡就這樣的。)
3.代碼

static void Main(string[] args)
{
//我僅負(fù)責(zé)聆聽,因為你來了,我就得接著等待下一個。
//(據(jù)說注釋寫成這樣的都是妖精!工作時候這樣寫能被打死不?等我找著工作了我試試)
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//綁定監(jiān)聽的端口
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 8070));
//開始聆聽你的請求
serverSocket.Listen(10);
while (true)
{
Console.WriteLine("等著請求");
//沒有請求的狀態(tài)下,程序就在這里停留。
//你來了,serverSocket就會把你的心愿告訴給一個新的socket。程序就繼續(xù)執(zhí)行了!(哎呀,文藝)
Socket socket = serverSocket.Accept();
Console.WriteLine("來了請求");
using (NetworkStream stream = new NetworkStream(socket))
using (StreamReader reader = new StreamReader(stream))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
if (line.Length <= 0)
{
break;//遇到空行了,請求結(jié)束了不用再等了
//如果不break,就一直卡在ReadLine()等著瀏覽器發(fā)后續(xù)的數(shù)據(jù)
}
}
}
using (NetworkStream stream = new NetworkStream(socket))
using (StreamWriter writer = new StreamWriter(stream))
{
writer.WriteLine("HTTP/1.1 200 OK");
writer.WriteLine();
writer.WriteLine("哎呀,你好,你好!");
}
socket.Disconnect(false);
}
}

4.調(diào)試
我們就這樣任性的、如qq自動回互般的,高冷的一直回答說:“哎呀,你好,你好!”。
![clipboard[2] clipboard[2]](http://image83.360doc.com/DownloadImg/2015/03/1708/51254062_4.png)
改進(jìn)(V2.0)
1.改進(jìn)需求
上一個版本的web服務(wù)器(姑且這么叫著。)根本沒有一點web服務(wù)器的樣子。聊天嘛,總不能一直“呵呵”下去。會沒朋友的!
所以新的web服務(wù)器改進(jìn)需求如下。
1.能夠獲取請求 路徑
2.能夠根據(jù)請求路徑對應(yīng)的文件,返回響應(yīng)的靜態(tài)頁面!
好歹有個正經(jīng)人家孩子的樣子嘛,老“呵呵”,成何體統(tǒng)。
我們話不多說,直接開始敲代碼吧!
2.實現(xiàn)

static void Main(string[] args)
{
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 8080));
serverSocket.Listen(10);
while (true)
{
Console.WriteLine("等著請求");
Socket socket = serverSocket.Accept();
Console.WriteLine("來了請求");
string firstLine;
using (NetworkStream stream = new NetworkStream(socket))
using (StreamReader reader = new StreamReader(stream))
{
//想1.0版本里多出了這么一句。
//想想http請求報文格式吧,第一行有文件路徑的喏!
firstLine = reader.ReadLine();
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
if (line.Length <= 0)
{
break;//遇到空行了,請求結(jié)束了不用再等了
//如果不break,就一直卡在ReadLine()等著瀏覽器發(fā)后續(xù)的數(shù)據(jù)
}
}
}
//獲取請求路徑
string[] strs = firstLine.Split(' ');
//url就獲取到了 類似index.html的這樣的串。
string url = strs[1];
Console.WriteLine("url=" + url);
using (NetworkStream stream = new NetworkStream(socket))
using (StreamWriter writer = new StreamWriter(stream))
{
//為什么要指定絕對路徑呢?想想正常web服務(wù)器里的【物理路徑】是啥意思。應(yīng)該就懂了。
string filePath = @"C:\Users\WinterT\Desktop\消息框、JBar" + url;
Console.WriteLine("filePath=" + filePath);
if (File.Exists(filePath))
{
writer.WriteLine("HTTP/1.1 200 OK");
writer.WriteLine();
string html =
File.ReadAllText(filePath);
Console.WriteLine(html);
writer.Write(html);
}
else
{
writer.WriteLine("HTTP/1.1 404 NOT FOUND");
writer.WriteLine();
writer.Write("404,沒有找到");
}
}
socket.Disconnect(false);
}
}

3.調(diào)試
請求的是我電腦里已有的一個html頁面。成功的顯示出來了!
![clipboard[3] clipboard[3]](http://image83.360doc.com/DownloadImg/2015/03/1708/51254062_5.png)
結(jié)束感言
![clipboard[4] clipboard[4]](http://pubimage.360doc.com/wz/default.gif)
![clipboard[5] clipboard[5]](http://pubimage.360doc.com/wz/default.gif)
![clipboard[6] clipboard[6]](http://pubimage.360doc.com/wz/default.gif)
(表示我很開心)
其實這次實驗,得出的結(jié)論依然是http協(xié)議是請求/響應(yīng)式的。傳遞的是文本!
就這個軟件而言,還有很多需要改進(jìn)的地方。 但這不是重點。重點是理解一些東西,做web服務(wù)器只是形式,而非目的!
PS:剛剛發(fā)現(xiàn),現(xiàn)在補(bǔ)充進(jìn)來。
不知道諸位注意到?jīng)]有。
我的web服務(wù)器,每次我從瀏覽器輸入url發(fā)出一個請求后,服務(wù)器的控制臺上都會顯示兩個請求。
那么多出來的那個請求是干啥的呢?我們再仔細(xì)看一下圖!

對,沒錯。瀏覽器在請求網(wǎng)站圖標(biāo)!
也就是說,我們想做網(wǎng)站圖標(biāo)的話,直接在網(wǎng)站根目錄下放一個做好的
favicon.ico文件就好了!!