轉:http://blog.csdn.net/sundacheng1989/article/details/28239711
http://blog.csdn.net/sundacheng1989/article/details/52437128
在C#編寫代碼,很多時候會遇到Http協議或者TCP協議,這里做一個簡單的理解。
TCP協議對應於傳輸層,而HTTP協議對應於應用層,從本質上來說,二者沒有可比性。Http協議是建立在TCP協議基礎之上的,當瀏覽器需要從服務器獲取網頁數據的時候,會發出一次Http請求。Http會通過TCP建立起一個到服務器的連接通道,當本次請求需要的數據完畢后,Http會立即將TCP連接斷開,這個過程是很短的。所以Http連接是一種短連接,是一種無狀態的連接。所謂的無狀態,是指瀏覽器每次向服務器發起請求的時候,不是通過一個連接,而是每次都建立一個新的連接。如果是一個連接的話,服務器進程中就能保持住這個連接並且在內存中記住一些信息狀態。而每次請求結束后,連接就關閉,相關的內容就釋放了,所以記不住任何狀態,成為無狀態連接。
隨着時間的推移,html頁面變得復雜了,里面可能嵌入了很多圖片,這時候每次訪問圖片都需要建立一次tcp連接就顯得低效了。因此Keep-Alive被提出用來解決效率低的問題。從HTTP/1.1起,默認都開啟了Keep-Alive,保持連接特性,簡單地說,當一個網頁打開完成后,客戶端和服務器之間用於傳輸HTTP數據的TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的連接Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。雖然這里使用TCP連接保持了一段時間,但是這個時間是有限范圍的,到了時間點依然是會關閉的,所以我們還把其看做是每次連接完成后就會關閉。后來,通過Session, Cookie等相關技術,也能保持一些用戶的狀態。但是還是每次都使用一個連接,依然是無狀態連接。
以前有個概念很容忍搞不清楚。就是為什么Http是無狀態的短連接,而TCP是有狀態的長連接?Http不是建立在TCP的基礎上嗎,為什么還能是短連接?現在明白了,Http就是在每次請求完成后就把TCP連接關了,所以是短連接。而我們直接通過Socket編程使用TCP協議的時候,因為我們自己可以通過代碼區控制什么時候打開連接什么時候關閉連接,只要我們不通過代碼把連接關閉,這個連接就會在客戶端和服務端的進程中一直存在,相關狀態數據會一直保存着。
在C#中會有Socket,實際上socket是對TCP/IP協議的封裝,Socket本身並不是協議,而是一個調用接口(API)。Socket的出現只是使得程序員更方便地使用TCP/IP協議棧而已,是對TCP/IP協議的抽象,從而形成了我們知道的一些最基本的函數接口,比如create、listen、connect、accept、send、read和write等等。
比較形象的描述:HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通信的能力。對於從C#編程的角度來講,為了方便,你可以直接選擇已經制造好的轎車Http來與服務器交互。但是有時候往往因為環境因素或者其他的一些定制的請求,必須要使用TCP協議,這時就需要使用Socket編程,然后自己去處理獲取的數據。就像是你用已有的發動機,自己造了一輛卡車,去從服務器交互。
HTTP/1.0和HTTP/1.1都把TCP作為底層的傳輸協議。HTTP客戶首先發起建立與服務器TCP連接。一旦建立連接,瀏覽器進程和服務器進程就可以通過各自的套接字來訪問TCP。如前所述,客戶端套接字是客戶進程和TCP連接之間的“門”,服務器端套接字是服務器進程和同一TCP連接之間的“門”。客戶往自己的套接字發送HTTP請求消息,也從自己的套接字接收HTTP響應消息。類似地,服務器從自己的套接字接收HTTP請求消息,也往自己的套接字發送HTTP響應消息。客戶或服務器一旦把某個消息送入各自的套接字,這個消息就完全落入TCP的控制之中。TCP給HTTP提供一個可靠的數據傳輸服務;這意味着由客戶發出的每個HTTP請求消息最終將無損地到達服務器,由服務器發出的每個HTTP響應消息最終也將無損地到達客戶。
C#代碼連接遠程數據庫用的是TCP協議。每次new 一個connection的時候,connection.open就打開了這個TCP連接。connection.Close的時候就關閉了這個連接。FTP的底層也是TCP, 不過是長連接的。傳輸大文件比較快。 需要看具體場景。在服務器端,如果程序是采取的長連接的方式,那么就能控制同時連接到這個服務器的連接個數,防止同時有多個連接。但是采取短連接的方式,那么就不能控制同時連接到這個服務器上的連接的個數,這也是一個優點,可以同時處理大量連接請求。但是如果連接請求量太大的話,可能造成服務器停止工作。
WebService不需要連接,一秒中至少可以支持上萬/十萬的請求,每次請求然后釋放,沒有空余的內存消耗。一般不會限制同時連接的個數,這是優勢。Message Queue需要建立連接, 支持上千的連接就很吃力了。因為每個連接即使沒有在請求數據,也會在內存中占用一定的空間存儲。會限制,比如SQL Server數據庫服務器,一般最多同時連接16個。
Http協議一定通過指定的端口,80,所以一般計算機上不會限制這個端口,所以Http協議能夠順利通過所有機器上的防火牆。而使用Socket編程的話,就需要自己指定特定的端口,那么很可能這個端口是在某個環境中禁用的,那么就無法穿透防火牆。IIS使用的是80端口,也就是這個程序一直在監聽着這個端口。一旦發現有人要建立到這個端口的連接,他就會響應,然后建立連接。這里說的連接都是短連接。所以你對服務器上的網址的請求,都是通過80端口送到網站程序的。然后通過這個端口發送的客戶端瀏覽器。
2016-09-05更新:
最近讀了讀《TCP/IP協議卷》,又寫了一篇后續文章,文章鏈接。
大約2年前寫了一篇關於HTTP協議與TCP協議的文章,原文鏈接。最近再次簡單讀了一遍《TCP/IP協議卷》,有了一些新的理解。這篇文章沒有一個很好的連貫性,都是我在讀書過程中總結的知識點,整體比較松散,但是個人感覺知識點都是非常重要,有很多地方讓我明白了迷惑很久的問題。
寫了這么長時間的代碼,發現自己對TCP/IP了解的並不是很透徹。雖然會用C#的HttpClient類來進行網絡編程,也可以使用Chrome的開發者工具來檢測每一次的HTTP請求的報文頭與報文體,也知道cookie的存在方式,但是對於這些數據怎么在網絡上傳輸還是很模糊,數據是怎么從客戶端的文件或者字符串轉換為二進制數並且傳送到服務器端的?為了弄明白這些問題,最近大致的讀了讀《TCP-IP詳解(卷一、二、三)》,也算是比以前清楚多了,下面是讀的過程中的一些知識點。
首先,我們要弄明白這個計算機網絡分層的概念。下邊這個圖是一個經典的分層描述,記得大學時候課本上的圖也跟這個差不多。
但是我更覺得,大家思想上都有一個抽象的概念,就是分層是垂直的,從上到下的。其實,我覺得,更准確的說,這個分層應該是水平的,從左到右的,就像車間的生產線,進去一個大的需要處理的原料,經過不同的操作台,一層一層的切割,包裝,到最后出來的時候就成為了很多精致的小產品。
關於網絡層。
網絡層有不同的協議,如IP與ICMP,兩者的不同就是對於上層傳過來的數據根據什么樣的格式進行切割,然后再次封裝時候遵循的准則不同。
ICMP是Ping命令經常用到的協議。Ping命令不是什么特別神秘的東西,是一個程序員編寫的一個exe應用程序,你的電腦控制台之所有能夠使用這個程序,是因為你的電腦上安裝了這個exe,而且在path里邊設置了這個程序的路徑。ICMP全稱是報文控制協議。通過上邊的圖片可以看出,應用層的Ping工具,使用Ping協議,直接跳過運輸層,調用了網絡層的ICMP協議。ICMP數據包里邊內容,都是關於目的主機的一些信息,因此可以用於遠程判斷一台主機是否存在於網絡上。ping程序是對兩個系統連通性進行測試的基本工具。它只利用ICMP回顯請求和回顯應答報文,而不用經過傳輸層TCP/UDP。Ping服務器一般在內核中實現ICMP的功能。
網絡上一台主機的可達性不僅僅取決於IP層是否可達,還要取決於使用何種協議以及端口號。就比如說,一台主機確實存在於互聯網上邊,而且一台Client向這台主機使用Ping工具發起ICMP協議包,這些數據包也准確到達了主機。主機在接收到這些數據包之后,從鏈路層傳到網絡層一層層拆去包裝進行解析,但是主機的操作系統從網絡層再往上解析的時候,發現了Ping的端口為6666(假設該主機封閉了該端口),就不會做出反應,而且默默的把這些數據吞了。那么在Client看來,發出去的數據包失聯了,會認為這個主機找不到。
所以,總結一下Ping不同可能的原因:主機不在線,比如說關機了或者拔掉網線了。還有就是網絡防火牆或者IP策略,會對ICMP報文進行過濾,ping命令無法回應,還有就是主機本身的一些策略,會過濾掉ICMP數據包。
(個人感覺操作系統以及網卡是這樣工作的,所有的網絡數據都是從一個入口進來的,進來之后操作系統與網卡相關的部件就開始從最底層開始解析這些二進制的數據包,一層層的拆包,組裝,然后分析,直到IP層的時候,會對IP數據包進行分析,然后進行TCP層的分析,這時候就發現了端口號這個概念,那么會根據端口號的不同,把這些數據存儲在不同的緩沖區域,每個緩沖區域屬於一個指定的應用程序(以端口號作為標識)。最終應用程序會從自己的緩沖區域來進行網絡數據的讀取。)
關於TCP的通信機制。
當TCP發出一個段后,它啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。TCP將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段(希望發端超時並重發)。既然TCP報文段作為IP數據報來傳輸,而IP數據報的到達可能會失序,因此TCP報文段的到達也可能會失序。如果必要, TCP將對收到的數據進行重新排序,將收到的數據以正確的順序交給應用層。
另外,TCP對字節流的內容不作任何解釋。TCP不知道傳輸的數據字節流是二進制數據,還是ASCII字符、EBCDIC字符或者其他類型數據。對字節流的解釋由TCP連接雙方的應用層解釋。這種對字節流的處理方式與Unix操作系統對文件的處理方式很相似。Unix的內核對一個應用讀或寫的內容不作任何解釋,而是交給應用程序處理。對Unix的內核來說,它無法區分一個二進制文件與一個文本文件。
(這里說一句題外話,就是ASCII碼與二進制文件的問題。最終保存在計算機硬盤上的數據都是二進制數據,那么這個二進制數據是怎么來的,這是一個問題。就拿txt文本文件來說,其存儲方式就是根據ASCII碼將文本內容轉換成相應的數字,然后用二進制的形式保存並且存儲。但是對於word等文件來說,比較復雜,有專門的軟件比如說Office來處理,並且有一定的算法來生成這些二進制。所以這就是為什么Word文件必須要用Office軟件來打開。Notepad是操作系統自帶的,如果用Notepad去打開word ,那么notepad就會根據ASCII碼的方式去解析,最終發現要么無法解析出來字符,要么解析出來的字符是亂碼。)
每個TCP段都包含源端和目的端的端口號,用於尋找發端和收端應用進程。這兩個值加上IP首部中的源端IP地址和目的端IP地址唯一確定一個TCP連接。一個IP地址和一個端口號也稱為一個插口socket.
既然一個TCP連接是全雙工(即數據在兩個方向上能同時傳遞),因此每個方向必須單獨地進行關閉。這原則就是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向連接。當一端收到一個FIN,它必須通知應用層另一端幾經終止了那個方向的數據傳送。發送FIN通常是應用層進行關閉的結果。
與Telnet類似,FTP最早的設計是用於兩台不同的主機,這兩個主機可能運行在不同的操作系統下、使用不同的文件結構、並可能使用不同字符集。但不同的是,Telnet獲得異構性是強制兩端都采用同一個標准:使用7比特ASCII碼的NVT。而FTP是采用另一種方法來處理不同系統間的差異。FTP支持有限數量的文件類型(A S C II,二進制,等等)和文件結構(面向字節流或記錄)。
在一次HTTP請求中,form表單的數據與上傳的文件數據有什么不同?
表單數據是根據ASCII碼轉換成的二進制,而上傳文件的時候,就是直接讀取的計算機硬盤上的二進制數據。比如說上傳一個Word文件,服務器端接收到的會是一大段二進制數據。其實文件在客戶端存儲的時候就是一大段二進制碼,那么這個二進制碼是怎么生成的?那么就要問微軟的Office客戶端了,是它根據一定的方式生成的二進制碼然后存在了硬盤上。所以,這就是為什么,一個exe生成的文件另外的exe打不開,因為使用的解碼方式不一樣,不知道怎么去分析這么一大堆的二進制碼,然后生成需要字符串展現給用戶。
端口號,不是說一個真正存在的實體,或者說在網卡上有個端口啥的。其實端口號就是一個簡單的數字標識,用於區分不同的應用程序,有點類似於應用程序的ID,因為網絡數據到達了一個主機上邊,怎么知道這個數據是給哪個應用程序的呢,這時候端口號就起作用了。前面已經指出過, TCP和UDP采用16bit的端口號來識別應用程序。那么這些端口號是如何選擇的呢?服務器一般都是通過知名端口號來識別的。例如,對於每個TCP/IP實現來說,FTP服務器的TCP端口號都是2 1,每個Telnet服務器的TCP端口號都是2 3,每個TFTP (簡單文件傳送)服務器的UDP端口號都是69。
客戶端通常對它所使用的端口號並不關心,只需保證該端口號在本機上是唯一的就可以了。客戶端口號又稱作臨時端口號(即存在時間很短暫)。這是因為它通常只是在用戶運行該客戶程序時才存在,而服務器則只要主機開着的,其服務就運行。
網絡層( IP)提供點到點的服務,而運輸層( T C P和U D P)提供端到端的服務。
在TCP/IP協議族中,網絡層IP提供的是一種不可靠的服務。也就是說,它只是盡可能快地把分組從源結點送到目的結點,但是並不提供任何可靠性保證。而另一方面, TCP在不可靠的IP層上提供了一個可靠的運輸層。為了提供這種可靠的服務, TCP采用了超時重傳、發送和接收端到端的確認分組等機制。由此可見,運輸層和網絡層分別負責不同的功能。
以前一直搞不懂,為什么IP層是不可靠的,而TCP是建立在IP的基礎上的,卻是可靠的呢?因為做了一些冗余的操作來保證可靠。Telnet和Rlogin這兩個交互應用要求最小的傳輸時延,因為人們主要用它們來傳輸少量的交互數據。另一方面,FTP文件傳輸則要求有最大的吞吐量。
同一個HTML頁面,從服務器端發送到客戶端瀏覽器,首先是根據HTTP協議,組裝字符串,組裝成一次請求回復,這個回復的字符串包括header,body等。然后這個字符串會被轉成二進制數據,然后給TCP層去分解,然后TCP層交給IP層,拆解成多個IP數據包。這時候這些包是無序的,不一定哪個包先到達。最終這些包再組成文件,如img,css,js文件。這就是為什么圖片渲染出來的順序不一樣。
IP層的下一層是數據鏈路層,我們也可以理解為以太網層或者令牌網。當一台主機把以太網數據幀發送到位於同一局域網上的另一台主機時,是根據48bit的以太網地址來確定目的接口的。設備驅動程序從不檢查IP數據報中的目的IP地址。ARP為IP地址到對應的硬件地址之間提供動態映射。我們之所以用動態這個詞是因為這個過程是自動完成的,一般應用程序用戶或系統管理員不必關心。
在硬件層次上進行的數據幀交換必須有正確的接口地址。但是,TCP/IP有自己的地址:32 bit的IP地址。知道主機的IP地址並不能讓內核發送一幀數據給主機。內核(如以太網驅動程序)必須知道目的端的硬件地址才能發送數據。ARP的功能是在32bit的IP地址和采用不同網絡技術的硬件地址之間提供動態映射。
獲取字符串的ASCII碼
string A = "Hello World";
byte[] data = Encoding.ASCII.GetBytes(A);
一次Http請求,會建立一個TCP連接,然后將內容切割,分組打包,最后發送到服務器。
以前有個疑問,就是總覺得進行TCP通信的A與B之間有個管道。如果A在發消息的時候,B也發送消息,那么內容在管道之中不就沖突了么。但是這種想法是錯誤的。A與B之間根本沒有管道,是通過IP層這種路由方式來進行數據包的轉換的,發送方與接收方根本都沒有指定的路線。發送與接收都是在不同的緩沖區,一般發消息的一方會在發送的內容中添加一個標識符,告訴接收方這次這一批的數據發送完了,你去處理吧,處理完了給我個回復。
當我們寫代碼的時候,有個讀取網絡數據的read方法,以前我一直以為是去網絡上都數據。這是錯誤的,這個read呢,就是去從緩沖區讀取已經被操作系統或者網卡拆箱並且還原了的數據,把這個數據讀取到程序的內存中。
為什么TCP建立連接會花費開銷?
這里並不是說要占用很多的互聯網上的帶寬,這里的花銷主要是指電腦上的資源消耗。建立TCP連接的時候,電腦要做很多的准備工作,建立相應的緩沖區域,根據端口號建立存儲區域,還有就是IP是不可靠的,TCP要想辦法找出空間來存儲一些額外的東西來保證可靠性,這都是開銷。
還是那句話,建立TCP通道,其實根本沒有通道,走的是IP路由,建立通道主要是在電腦內存上開辟出相應的空間。TCP連接一直存在,說明那塊相應的緩存區域一直沒有被回收。
A與B之間是怎么建立起TCP連接的?
這個就涉及到了3次握手機制。因為B機器上有程序在時刻監視着所有的IP數據包,一旦檢測到數據包中含有3次握手的內容,便會打開一個連接,然后通過身份驗證等機制,最終建立起TCP連接。