從應用的角度出發,協議可理解爲「規則」,是數據傳輸和數據的解釋的規則。html
假設,A、B雙方欲傳輸文件。規定: 第一次,傳輸文件名,接收方接收到文件名,應答OK給傳輸方; 第二次,發送文件的尺寸,接收方接收到該數據再次應答一個OK; 第三次,傳輸文件內容。一樣,接收方接收數據完成後應答OK表示文件內容接收成功。面試
由此,不管A、B之間傳遞何種文件,都是經過三次數據傳輸來完成。A、B之間造成了一個最簡單的數據傳輸規則。雙方都按此規則發送、接收數據。A、B之間達成的這個相互遵照的規則即爲協議。編程
這種僅在A、B之間被遵照的協議稱之爲原始協議。瀏覽器
當此協議被更多的人採用,不斷的增長、改進、維護、完善。最終造成一個穩定的、完整的文件傳輸協議,被普遍應用於各類文件傳輸過程當中。該協議就成爲一個標準協議。最先的ftp協議就是由此衍生而來。bash
爲了減小協議設計的複雜性,大多數網絡模型均採用分層的方式來組織。每一層都有本身的功能,就像建築物同樣,每一層都靠下一層支持。每一層利用下一層提供的服務來爲上一層提供服務,本層服務的實現細節對上層屏蔽。 服務器
越下面的層,越靠近硬件;越上面的層,越靠近用戶。至於每一層叫什麼名字,其實並不重要(面試的時候,面試官可能會問每一層的名字)。只須要知道,互聯網分紅若干層便可。微信
物理層:主要定義物理設備標準,如網線的接口類型、光纖的接口類型、各類傳輸介質的傳輸速率等。它的主要做用是傳輸比特流(就是由一、0轉化爲電流強弱來進行傳輸,到達目的地後再轉化爲一、0,也就是咱們常說的數模轉換與模數轉換)。這一層的數據叫作比特。 數據鏈路層:定義瞭如何讓格式化數據以幀爲單位進行傳輸,以及如何讓控制對物理介質的訪問。這一層一般還提供錯誤檢測和糾正,以確保數據的可靠傳輸。如:串口通訊中使用到的115200、八、N、1 網絡層:在位於不一樣地理位置的網絡中的兩個主機系統之間提供鏈接和路徑選擇。Internet的發展使得從世界各站點訪問信息的用戶數大大增長,而網絡層正是管理這種鏈接的層。 傳輸層:定義了一些傳輸數據的協議和端口號(WWW端口80等),如:TCP(傳輸控制協議,傳輸效率低,可靠性強,用於傳輸可靠性要求高,數據量大的數據),UDP(用戶數據報協議,與TCP特性偏偏相反,用於傳輸可靠性要求不高,數據量小的數據,如QQ聊天數據就是經過這種方式傳輸的)。 主要是將從下層接收的數據進行分段和傳輸,到達目的地址後再進行重組。經常把這一層數據叫作段。 會話層:經過傳輸層(端口號:傳輸端口與接收端口)創建數據傳輸的通路。主要在你的系統之間發起會話或者接受會話請求(設備之間須要互相認識能夠是IP也能夠是MAC或者是主機名)。 表示層:可確保一個系統的應用層所發送的信息能夠被另外一個系統的應用層讀取。例如,PC程序與另外一臺計算機進行通訊,其中一臺計算機使用擴展二一十進制交換碼(EBCDIC),而另外一臺則使用美國信息交換標準碼(ASCII)來表示相同的字符。若有必要,表示層會經過使用一種通格式來實現多種數據格式之間的轉換。 應用層:是最靠近用戶的OSI層。這一層爲用戶的應用程序(例如電子郵件、文件傳輸和終端仿真)提供網絡服務。網絡
每一層都是爲了完成一種功能,爲了實現這些功能,就須要你們都遵照共同的規則。你們都遵照這規則,就叫作「協議」(protocol)。架構
網絡的每一層,都定義了不少協議。這些協議的總稱,叫「TCP/IP協議」。TCP/IP協議是一個你們族,不只僅只有TCP和IP協議,它還包括其它的協議,以下圖: app
鏈路層 以太網規定,連入網絡的全部設備,都必須具備「網卡」接口。數據包必須是從一塊網卡,傳送到另外一塊網卡。經過網卡可以使不一樣的計算機之間鏈接,從而完成數據通訊等功能。網卡的地址——MAC 地址,就是數據包的物理髮送地址和物理接收地址。
網絡層 網絡層的做用是引進一套新的地址,使得咱們可以區分不一樣的計算機是否屬於同一個子網絡。這套地址就叫作「網絡地址」,這是咱們平時所說的IP地址。這個IP地址比如咱們的手機號碼,經過手機號碼能夠獲得用戶所在的歸屬地。
網絡地址幫助咱們肯定計算機所在的子網絡,MAC 地址則將數據包送到該子網絡中的目標網卡。網絡層協議包含的主要信息是源IP和目的IP。
因而,「網絡層」出現之後,每臺計算機有了兩種地址,一種是 MAC 地址,另外一種是網絡地址。兩種地址之間沒有任何聯繫,MAC 地址是綁定在網卡上的,網絡地址則是管理員分配的,它們只是隨機組合在一塊兒。
網絡地址幫助咱們肯定計算機所在的子網絡,MAC 地址則將數據包送到該子網絡中的目標網卡。所以,從邏輯上能夠推斷,一定是先處理網絡地址,而後再處理 MAC 地址。
也就是說,咱們還須要一個參數,表示這個數據包到底供哪一個程序(進程)使用。這個參數就叫作「端口」(port),它實際上是每個使用網卡的程序的編號。每一個數據包都發到主機的特定端口,因此不一樣的程序就能取到本身所須要的數據。
端口特色: 對於同一個端口,在不一樣系統中對應着不一樣的進程 對於同一個系統,一個端口只能被一個進程擁有
Socket起源於Unix,而Unix基本哲學之一就是「一切皆文件」,均可以用「打開open –> 讀寫write/read –> 關閉close」模式來操做。Socket就是該模式的一個實現,網絡的Socket數據傳輸是一種特殊的I/O,Socket也是一種文件描述符。Socket也具備一個相似於打開文件的函數調用:Socket(),該函數返回一個整型的Socket描述符,隨後的鏈接創建、數據傳輸等操做都是經過該Socket實現的。
經常使用的Socket類型有兩種:流式Socket(SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向鏈接的Socket,針對於面向鏈接的TCP服務應用;數據報式Socket是一種無鏈接的Socket,對應於無鏈接的UDP服務應用。
package main
import (
"fmt"
"log"
"net"
"strings"
)
func dealConn(conn net.Conn) {
defer conn.Close() //此函數結束時,關閉鏈接套接字
//conn.RemoteAddr().String():鏈接客服端的網絡地址
ipAddr := conn.RemoteAddr().String()
fmt.Println(ipAddr, "鏈接成功")
buf := make([]byte, 1024) //緩衝區,用於接收客戶端發送的數據
for {
//阻塞等待用戶發送的數據
n, err := conn.Read(buf) //n代碼接收數據的長度
if err != nil {
fmt.Println(err)
return
}
//切片截取,只截取有效數據
result := buf[:n]
fmt.Printf("接收到數據來自[%s]==>[%d]:%s\n", ipAddr, n, string(result))
if "exit" == string(result) { //若是對方發送"exit",退出此連接
fmt.Println(ipAddr, "退出鏈接")
return
}
//把接收到的數據轉換爲大寫,再給客戶端發送
conn.Write([]byte(strings.ToUpper(string(result))))
}
}
func main() {
//建立、監聽socket
listenner, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatal(err) //log.Fatal()會產生panic
}
defer listenner.Close()
for {
conn, err := listenner.Accept() //阻塞等待客戶端鏈接
if err != nil {
log.Println(err)
continue
}
go dealConn(conn)
}
}
複製代碼
package main
import (
"fmt"
"log"
"net"
)
func main() {
//客戶端主動鏈接服務器
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatal(err) //log.Fatal()會產生panic
return
}
defer conn.Close() //關閉
buf := make([]byte, 1024) //緩衝區
for {
fmt.Printf("請輸入發送的內容:")
fmt.Scan(&buf)
fmt.Printf("發送的內容:%s\n", string(buf))
//發送數據
conn.Write(buf)
//阻塞等待服務器回覆的數據
n, err := conn.Read(buf) //n代碼接收數據的長度
if err != nil {
fmt.Println(err)
return
}
//切片截取,只截取有效數據
result := buf[:n]
fmt.Printf("接收到數據[%d]:%s\n", n, string(result))
}
}
複製代碼
咱們平時瀏覽網頁的時候,會打開瀏覽器,輸入網址後按下回車鍵,而後就會顯示出你想要瀏覽的內容。在這個看似簡單的用戶行爲背後,到底隱藏了些什麼呢?
對於普通的上網過程,系統實際上是這樣作的:瀏覽器自己是一個客戶端,當你輸入URL的時候,首先瀏覽器會去請求DNS服務器,經過DNS獲取相應的域名對應的IP,而後經過IP地址找到IP對應的服務器後,要求創建TCP鏈接,等瀏覽器發送完HTTP Request(請求)包後,服務器接收到請求包以後纔開始處理請求包,服務器調用自身服務,返回HTTP Response(響應)包;客戶端收到來自服務器的響應後開始渲染這個Response包裏的主體(body),等收到所有的內容隨後斷開與該服務器之間的TCP鏈接。
一個Web服務器也被稱爲HTTP服務器,它經過HTTP協議與客戶端通訊。這個客戶端一般指的是Web瀏覽器(其實手機端客戶端內部也是瀏覽器實現的)。
Web服務器的工做原理能夠簡單地概括爲: 客戶機經過TCP/IP協議創建到服務器的TCP鏈接 客戶端向服務器發送HTTP協議請求包,請求服務器裏的資源文檔 服務器向客戶機發送HTTP協議應答包,若是請求的資源包含有動態語言的內容,那麼服務器會調用動態語言的解釋引擎負責處理「動態內容」,並將處理獲得的數據返回給客戶端 客戶機與服務器斷開。由客戶端解釋HTML文檔,在客戶端屏幕上渲染圖形結果
超文本傳輸協議(HTTP,HyperText Transfer Protocol)是互聯網上應用最爲普遍的一種網絡協議,它詳細規定了瀏覽器和萬維網服務器之間互相通訊的規則,經過因特網傳送萬維網文檔的數據傳送協議。
HTTP協議一般承載於TCP協議之上,有時也承載於TLS或SSL協議層之上,這個時候,就成了咱們常說的HTTPS。以下圖所示:
URL全稱爲Unique Resource Location,用來表示網絡資源,能夠理解爲網絡文件路徑。
URL的格式以下: http://host[":"port][abs_path]
http://192.168.31.1/html/index
URL的長度有限制,不一樣的服務器的限制值不太相同,可是不能無限長。
package main
import (
"fmt"
"log"
"net"
)
func main() {
//建立、監聽socket
listenner, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatal(err) //log.Fatal()會產生panic
}
defer listenner.Close()
conn, err := listenner.Accept() //阻塞等待客戶端鏈接
if err != nil {
log.Println(err)
return
}
defer conn.Close() //此函數結束時,關閉鏈接套接字
//conn.RemoteAddr().String():鏈接客服端的網絡地址
ipAddr := conn.RemoteAddr().String()
fmt.Println(ipAddr, "鏈接成功")
buf := make([]byte, 4096) //緩衝區,用於接收客戶端發送的數據
//阻塞等待用戶發送的數據
n, err := conn.Read(buf) //n代碼接收數據的長度
if err != nil {
fmt.Println(err)
return
}
//切片截取,只截取有效數據
result := buf[:n]
fmt.Printf("接收到數據來自[%s]==>:\n%s\n", ipAddr, string(result))
}
複製代碼
瀏覽器輸入url地址:
服務器端運行打印結果以下:
請求行 請求行由方法字段、URL 字段 和HTTP 協議版本字段 3 個部分組成,他們之間使用空格隔開。經常使用的 HTTP 請求方法有 GET、POST。
GET: 當客戶端要從服務器中讀取某個資源時,使用GET 方法。GET 方法要求服務器將URL 定位的資源放在響應報文的數據部分,回送給客戶端,即向服務器請求某個資源。 使用GET方法時,請求參數和對應的值附加在 URL 後面,利用一個問號(「?」)表明URL 的結尾與請求參數的開始,傳遞參數長度受限制,所以GET方法不適合用於上傳數據。 經過GET方法來獲取網頁時,參數會顯示在瀏覽器地址欄上,所以保密性不好。
POST: 當客戶端給服務器提供信息較多時可使用POST 方法,POST 方法向服務器提交數據,好比完成表單數據的提交,將數據提交給服務器處理。 GET 通常用於獲取/查詢資源信息,POST 會附帶用戶數據,通常用於更新資源信息。POST 方法將請求參數封裝在HTTP 請求數據中,並且長度沒有限制,由於POST攜帶的數據,在HTTP的請求正文中,以名稱/值的形式出現,能夠傳輸大量數據。
請求頭部 請求頭部爲請求報文添加了一些附加信息,由「名/值」對組成,每行一對,名和值之間使用冒號分隔。 請求頭部通知服務器有關於客戶端請求的信息,典型的請求頭有:
空行 最後一個請求頭以後是一個空行,發送回車符和換行符,通知服務器如下再也不有請求頭。
請求包體 請求包體不在GET方法中使用,而是POST方法中使用。 POST方法適用於須要客戶填寫表單的場合。與請求包體相關的最常使用的是包體類型Content-Type和包體長度Content-Length。
package main
import (
"fmt"
"net/http"
)
//服務端編寫的業務邏輯處理程序
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
http.HandleFunc("/go", myHandler)
//在指定的地址進行監聽,開啓一個HTTP
http.ListenAndServe("127.0.0.1:8000", nil)
}
複製代碼
啓動服務器程序:
客戶端測試示例代碼:
package main
import (
"fmt"
"log"
"net"
)
func main() {
//客戶端主動鏈接服務器
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatal(err) //log.Fatal()會產生panic
return
}
defer conn.Close() //關閉
requestHeader := "GET /go HTTP/1.1\r\nAccept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/xaml+xml, application/x-ms-xbap, */*\r\nAccept-Language: zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)\r\nAccept-Encoding: gzip, deflate\r\nHost: 127.0.0.1:8000\r\nConnection: Keep-Alive\r\n\r\n"
//先發送請求包
conn.Write([]byte(requestHeader))
buf := make([]byte, 4096) //緩衝區
//阻塞等待服務器回覆的數據
n, err := conn.Read(buf) //n代碼接收數據的長度
if err != nil {
fmt.Println(err)
return
}
//切片截取,只截取有效數據
result := buf[:n]
fmt.Printf("接收到數據[%d]:\n%s\n", n, string(result))
}
複製代碼
啓動程序,測試http的成功響應報文:
啓動程序,測試http的失敗響應報文:
狀態行 狀態行由 HTTP 協議版本字段、狀態碼和狀態碼的描述文本3個部分組成,他們之間使用空格隔開。
狀態碼: 狀態碼由三位數字組成,第一位數字表示響應的類型,經常使用的狀態碼有五大類以下所示:
常見的狀態碼舉例:
響應頭部 響應頭可能包括:
空行 最後一個響應頭部以後是一個空行,發送回車符和換行符,通知服務器如下再也不有響應頭部。
響應包體 服務器返回給客戶端的文本信息。
Go語言標準庫內建提供了net/http包,涵蓋了HTTP客戶端和服務端的具體實現。使用 net/http包,咱們能夠很方便地編寫HTTP客戶端或服務端的程序。
示例代碼:
package main
import (
"fmt"
"net/http"
)
//服務端編寫的業務邏輯處理程序
//hander函數: 具備func(w http.ResponseWriter, r *http.Requests)簽名的函數
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.RemoteAddr, "鏈接成功") //r.RemoteAddr遠程網絡地址
fmt.Println("method = ", r.Method) //請求方法
fmt.Println("url = ", r.URL.Path)
fmt.Println("header = ", r.Header)
fmt.Println("body = ", r.Body)
w.Write([]byte("hello go")) //給客戶端回覆數據
}
func main() {
http.HandleFunc("/go", myHandler)
//該方法用於在指定的 TCP 網絡地址 addr 進行監聽,而後調用服務端處理程序來處理傳入的鏈接請求。
//該方法有兩個參數:第一個參數 addr 即監聽地址;第二個參數表示服務端處理程序,一般爲空
//第二個參數爲空意味着服務端調用 http.DefaultServeMux 進行處理
http.ListenAndServe("127.0.0.1:8000", nil)
}
複製代碼
瀏覽器輸入url地址:
服務器運行結果:
package main
import (
"fmt"
"io"
"log"
"net/http"
)
func main() {
//get方式請求一個資源
//resp, err := http.Get("http://www.baidu.com")
//resp, err := http.Get("http://www.neihan8.com/article/index.html")
resp, err := http.Get("http://127.0.0.1:8000/go")
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close() //關閉
fmt.Println("header = ", resp.Header)
fmt.Printf("resp status %s\nstatusCode %d\n", resp.Status, resp.StatusCode)
fmt.Printf("body type = %T\n", resp.Body)
buf := make([]byte, 2048) //切片緩衝區
var tmp string
for {
n, err := resp.Body.Read(buf) //讀取body包內容
if err != nil && err != io.EOF {
fmt.Println(err)
return
}
if n == 0 {
fmt.Println("讀取內容結束")
break
}
tmp += string(buf[:n]) //累加讀取的內容
}
fmt.Println("buf = ", string(tmp))
}
複製代碼