之前我們實現了一個自己的應用程式層的協議,功能非常簡單,只包括了最基本的成幀和解析功能。不過有了這些基礎,我們再返回來看看現在在互連網上最通行的http協議,就會容易懂得許多。
http具體是做什麼的,網上面講解很多,比如:
我們知道,Internet的基本協議是TCP/IP協議,然而在TCP/IP模型最上層的是應用程式層(Application layer),它包含所有高層的協議。高層協議有:檔案傳輸通訊協定FTP、電子郵件傳輸協議SMTP、網路新聞傳輸通訊協定NNTP和HTTP協議等。
HTTP協議(HyperText Transfer Protocol,超文字傳輸通訊協定 (HTTP))是用於從WWW伺服器傳輸超文本到本地瀏覽器的傳輸協議。它可以使瀏覽器更加高效,使網路傳輸減少。它不僅保證電腦 正確快速地傳輸超文字文件,還確定傳輸文檔中的哪一部分,以及哪部分內容首先顯示(如文本先於圖形)等。這就是你為什麼在瀏覽器中看到的網頁地址都是以 http://開頭的原因。
當決定使用超文本作為WWW文檔的標準格式後,於是在1990年,科學家們立即制定了能夠快速尋找這些超文字文件的協議,即 HTTP協議。經過幾年的使用與發展,得到不斷的完善和擴充,目前在WWW中使用的是HTTP/1.1,持久串連被預設採用。支援以管道方式同時發送多個請求,以便降低線路負載,提高傳輸速度。在Proxy 伺服器上1.0版本較多。
簡而言之,Http是一種基於Request/Response模式的,無狀態的協議。理解這兩個關鍵詞是重中之重,下面會有詳細解釋。
HTTP報文由從客戶機到伺服器的請求和從伺服器到客戶機的響應構成。請求報文格式如下:
請求行 - 通用資訊頭 - 要求標頭 - 實體頭 - 報文主體
請求行以方法欄位開始,後面分別是 URL 欄位和 HTTP 協議版本欄位,並以 CRLF 結尾。SP 是分隔字元。除了在最後的 CRLF 序列中 CF 和 LF 是必需的之外,其他都可以不要。
應答報文格式如下:
狀態行 - 通用資訊頭 - 回應標頭 - 實體頭 - 報文主體
狀態代碼元由3位元字組成,表示請求是否被理解或被滿足。原因分析是對原文的狀態代碼作簡短的描述,狀態代碼用來支援自動操作,而原因分析用來供使用者使用。客戶機無需用來檢查或顯示文法。
我們還是來自己動手寫個最基本例子,用來說明http協議。
注意我們除了本文之外,還發送了head,分隔行,和狀態資訊。還記得我們在自己實現得協議裡的訊息長度吧。其實這些就是為了處理資料和告訴應該的伺服器響應動作而提供的中繼資料。
publicclass SimpleSocketListener
{
publicvoid Run()
{
// 取得原生 loopback 網路地址,即 127.0.0.1
IPAddress address = IPAddress.Loopback;
IPEndPoint endPoint =new IPEndPoint(address, 9527);
// 建立一個 socket,傳輸協議為TCP
Socket socket =new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
// 將 socket 綁定到一個端點上
socket.Bind(endPoint);
socket.Listen(10);
Console.WriteLine("開始監聽, 連接埠號碼:{0}.", endPoint.Port);
while (true)
{
Socket client = socket.Accept();
Console.WriteLine(client.RemoteEndPoint);
byte[] buffer =newbyte[1028];
int length = client.Receive(buffer, 1028, SocketFlags.None);
System.Text.Encoding utf8 = System.Text.Encoding.UTF8;
string requestString = utf8.GetString(buffer, 0, length);
Console.WriteLine(requestString);
// 狀態行
string statusLine ="HTTP/1.1 200 OK\r\n";
byte[] statusLineBytes = utf8.GetBytes(statusLine);
// 準備發送到用戶端的相應內容
string responseBody
=@"<html>
<body><h1>Content from server!</h1></body>
</html>";
byte[] responseBodyBytes = utf8.GetBytes(responseBody);
// 回應的頭部
string responseHeader =
string.Format(
"Content-Type: text/html; charset=UTF-8\r\nContent-Length: {0}\r\n",
responseBody.Length //看到長度是否很熟悉?
);
byte[] responseHeaderBytes = utf8.GetBytes(responseHeader);
// 向用戶端發送狀態資訊
client.Send(statusLineBytes);
// 向用戶端發送回應頭
client.Send(responseHeaderBytes);
// 頭部與內容的分隔行
client.Send(newbyte[] { 13, 10 });
// 向用戶端發送內容部分
client.Send(responseBodyBytes);
// 斷開與用戶端的串連
client.Close();
if (Console.KeyAvailable)
break;
}
// 關閉伺服器
socket.Close();
}
}
其他剩下的工作瀏覽器都幫我們做好了,在瀏覽器中輸入地址,發送請求
有人也許會問,Http不是基於TCP/IP的嗎?而這個是可以保持狀態的。怎麼Http就是無狀態了的呢?搞清楚這個問題,對以後我們WCF中選擇協議也有協助。
Http是屬於最高層的應用協議,基於TCP/IP,也就是說它在TCP/IP的基礎上引入了新的概念和規定。因此,無狀態是Http規定的,是為了適應Web的要求而規定的。Web應用經常面對大量的訪問,如果都保持TCP的串連狀態那麼將會消耗大量的資源。就會演變成了類似“用戶端/服務端”一對一模型。因此,Http規定了它是無狀態的,也就是說,處理完一個請求並返回以後,伺服器端就要直接關閉掉TCP串連,不管相同的用戶端是否再次發送請求。以這樣的形式來實現單向的Request/response模式的。
上面我說的是Http 1.0 的規定。用圖來表示就是:
注意,服務端關閉串連這個動作不可少。這樣才是無狀態。也就是說HTTP 1.0的協議的無狀態是由這4個步驟組成,缺一不可。一個串連請求就在一次處理後關閉。
看到這裡如果對瀏覽器有些瞭解的人就會知道,其實這個模型並不高效。在當初網頁主要是HTML文本的時候還可以,但是現在一個的網頁裡都包含了非常多的諸如img,javascript,css等元素,這使得瀏覽器在解析由伺服器發回的HTML網頁以後,如果解析HTML時發現了上面這些元素的標籤,那麼還要多次的發起串連請求。而每次建立串連請求都需要三向交握,比較耗資源。因此,HTTP 1.0對於現在的網頁來說並不很適合。
瀏覽器產生一個完整的頁面,一共發送了N+1條請求。1就是指返回的HTML網頁字串,N是其中包含的資源,由瀏覽器解析HTML標籤後發出。
:
這個問題應該怎麼解決呢?要是能夠在一條已建立的串連上,多處理幾次資源請求就好了。也就是串連能夠複用,更直接了當的說就是當伺服器處理完一條資源請求時,不要像1.0那樣馬上關閉串連,而是等一段時間。
持久串連
在1.0+(1.0的各種修正版本)以及1.1中,允許HTTP裝置在交易處理結束以後仍然將TCP保持在開啟的狀態,以便為未來的HTTP請求重用現存的串連。在交易處理結束以後仍然保持開啟的串連我們就叫做“持久串連”,持久串連在1.0+和1.1中是通過不同方式確定的:
- HTTP 1.0中通過首部插入keep-alive資訊表明是持久串連
- HTTP 1.1中預設是持久串連
下面來介紹一下這兩種方式
1.keep-alive
用戶端 :實現HTTP/1.0 keep-alive串連的用戶端可以通過包含connection:Keep-Alive首部請求將一條串連保持在開啟狀態。
伺服器端:如果伺服器端願意將一條串連保持在開啟狀態,那麼就在回應標頭部中同樣插入connection:Keep-Alive ,否則表達的意思就是我響應你這條資訊後我就要把TCP串連給關閉掉。
HTTP 1.0 中keep-alive不是預設採用的,也就是說如果你不主動的往頭部加入這個資訊,那麼就意味著是一次HTTP請求就會開啟關閉一次TCP串連。用戶端通過檢查伺服器端的響應有沒有connection:Keep-Alive,就可以知道伺服器發出響應以後是否會關閉串連了。
2.和keep-alive正好相反,在HTTP1.1中持久串連預設是啟用的,除非你發送了Connection:close表明可以關閉串連。HTTP1.1用戶端假定在收到響應之後,除非響應中包含了Conncetion:close,否則HTTP/1.1的串連就仍然維持在開啟的狀態。
因此,這時伺服器對於關閉TCP串連就會採用兩種方式,一種是當收到瀏覽器發送的關閉串連請求後關閉該TCP請求(意味著瀏覽器已經把該網頁所需的請求資源已經拿完了,無需再從這條串連裡擷取其他資源),另一種就是當經過一段時間後,就算瀏覽器沒有發送關閉串連的請求,服務端也會因為逾時而自動關閉該條TCP串連。
因此我們知道為什麼我們的HTTP請求中還包含了HTTP的版本資訊,因為這會影響到雙發對於關閉請求的理解。
當然HTTP 1.1除了這條改進之外,還增加了諸如PUT,DELETE等動作。在接下來講REST的文章的時候會講到。
如果你覺得這邊文章有協助,請點擊一下推薦,謝謝!