Go語言TCP從原理到代碼實現(xiàn)詳解
引言
基于net
包的小應(yīng)用
完整代碼已經(jīng)上傳到github
GitHub-TCP
歡迎star
和issue
TCP介紹
特點
- 面向連接的運輸層協(xié)議。在應(yīng)用程序在使用TCP協(xié)議之前,必須先建立TCP連接。在傳送數(shù)據(jù)完畢后,必須釋放已經(jīng)建立的TCP連接。
- 每一條TCP連接只能有兩個端點,每一條TCP連接只能是點對點的。
- TCP提供可靠交付的服務(wù)。 通過TCP連接傳送的數(shù)據(jù),無差錯,不丟失,不重復(fù),并且按序到達。
- TCP提供全雙工通信。 TCP允許通信雙方的應(yīng)用進程在任何時候都能發(fā)送數(shù)據(jù)。
- 面向字節(jié)流。 TCP的流是指流入到進程或從進程流出的字節(jié)序列。雖然應(yīng)用程序和TCP的交互式一次一個數(shù)據(jù)塊,但TCP把應(yīng)用程序交下來的數(shù)據(jù)僅僅看成是一連串的無結(jié)構(gòu)的字節(jié)流。
圖解
- TCP結(jié)構(gòu)
- TCP連接
TCP 連接建立,三次握手
傳輸控制塊TCB:存儲了每一個連接中的一些重要信息。比如TCP連接表,指向發(fā)送和接收緩沖的指針,指向重傳隊列的指針,當(dāng)前的發(fā)送和接收序列等等。
假設(shè)主機A是TCP客戶程序,B是TCP服務(wù)器程序。最初兩端的TCP進程都是處于CLOSED關(guān)閉狀態(tài),客戶端A打開鏈接,服務(wù)器端被打開鏈接。一開始B的TCP服務(wù)器進程先創(chuàng)建傳輸控制塊TCB,準(zhǔn)備接受客戶進程的鏈接請求,然后服務(wù)器進程就處于LISTEN
收聽狀態(tài),等待A的連接請求。
- 然后A的進程首先創(chuàng)建傳輸控制模塊TCB。向B發(fā)出連接請求報文段,這是首部當(dāng)中的同步位
SYN=1
,同時選擇一個初始序號seq=x
。TCP規(guī)定,SYN報文段
(即SYN=1
的報文段)不能寫數(shù)據(jù),但要消耗掉一個序號。這時候A就進入了同步已發(fā)送的狀態(tài)。 - B收到連接請求報文段后,如果同意建立連接,則向A發(fā)送確認,在確認報文段中把
SYN
位和AVK
位置都置為1
,確認號為ack+1
,同時也為自己選擇一個初始序號y。同樣的這個報文段也是不能寫數(shù)據(jù)的,但同時要消耗掉一個序號。這時B進入了同步收到狀態(tài)。 - A收到B的確認之后,還要向B給出確認。確認報文段的
ACK
置1,確認號ack=y+1
,而自己的seq=x+1
。ACK報文段是可以攜帶數(shù)據(jù)的,但如果不攜帶數(shù)據(jù)則不消耗序號,在這種情況下,下一個數(shù)據(jù)報文段的序號仍為seq=x+1
。
這時候TCP已經(jīng)建立了。A進行入了已經(jīng)建立連接的階段狀態(tài)。B收到確認后也進入了連接狀態(tài)。
TCP 連接釋放,四次揮手
數(shù)據(jù)傳輸完畢之后,通信的雙方都可釋放連接?,F(xiàn)在A和B都處于ESTABLISHED
狀態(tài)。
- A的應(yīng)用進程先向TCP發(fā)出連接釋放報文段,并停止再發(fā)送數(shù)據(jù),主動關(guān)閉TCP連接。A把鏈接釋放報文段首部的終止控制位
FIN
置為1
,其序號為seq=u
,它等于前面以傳送過的數(shù)據(jù)的最后一個字節(jié)的序號加1
.這時候A進入了FIN-WAIT-1(終止等待1)
狀態(tài),等待B的確認。
注意:TCP規(guī)定,F(xiàn)IN報文段即使不攜帶數(shù)據(jù),他也消耗掉一個序號??!
- B 收到鏈接釋放報文段后即發(fā)出確認,確認號是
ack = u + 1
,而這個報文段自己的序號是v
,等于B前面已傳送過的數(shù)據(jù)的最后一個字節(jié)的序號加1
.然后B就進入CLOSE-WAIT(關(guān)閉等待)
狀態(tài)。TCP服務(wù)器進程這時應(yīng)通知高層應(yīng)用進程,因而從A到B這個方向的鏈接就釋放了,這時的TCP鏈接處于半關(guān)閉狀態(tài),即A已經(jīng)沒有數(shù)據(jù)要發(fā)送了,但B若發(fā)送數(shù)據(jù),A仍要接收,也就是說,從B到A這個方向的連接并未關(guān)閉。這個狀態(tài)可能要維持一段時間。 - A收到來自B的確認后,就進入了
FIN-WAIT-2(終止等待2)
狀態(tài)滿等待B發(fā)出的連接釋放報文段。若B已經(jīng)沒有要向A發(fā)送的數(shù)據(jù),其應(yīng)用進程就通知TCP釋放連接,這時B發(fā)出的連接釋放報文段必須使FIN = 1
,現(xiàn)假定B的序號為w
(在半關(guān)閉狀態(tài)B可能又發(fā)送了一些數(shù)據(jù))。B還必須重復(fù)上次已發(fā)送過的確認號ack = u + 1
.這時B就進入LAST-ACK(最后確認)
狀態(tài),等待A的確認。 - A在收到了B的鏈接釋放報文段后,必須對此發(fā)出確認。在確認報文段中把
ACK置1
,確認號ack=w+1
,而自己的序號是seq=u+1
(根據(jù)TCP標(biāo)準(zhǔn),前面發(fā)送過的FIN報文段要消耗一個序號)。然后進入到TIME-WAIT
(時間等待)狀態(tài)。注意: 現(xiàn)在TCP連接還沒有還沒有釋放掉
。必須經(jīng)過時間等待計時器設(shè)置的時間2MSL
后,A才能進入CLOSED狀態(tài)。
時間MSL叫做最長報文段壽命,RFC793建議設(shè)在兩分鐘。但是在現(xiàn)在工程來看兩分鐘太長了,所以TCP允許不同的實現(xiàn)可以根據(jù)具體情況使用更小的MSL值。
代碼實現(xiàn)
首先創(chuàng)建兩個目錄,一個是client
客戶端,另一個是server
服務(wù)端。
1. 連接
1.1 服務(wù)端
- 監(jiān)聽連接
net
中提供了Listen
方法,可以讓服務(wù)端進行端口監(jiān)聽
ADDRESS := "127.0.0.1:5000" listener,err := net.Listen("tcp",ADDRESS) if err != nil { fmt.Printf("start tcp server %s failed ,err : %s ",listener,err) return } defer listener.Close()
1.2 客戶端
- 建立連接
net
中提供了Dail
方法,讓客戶端連接服務(wù)端
ADDRESS := "127.0.0.1:5000" conn,err := net.Dial("tcp",ADDRESS) // 主動與服務(wù)端建立連接 if err != nil { fmt.Printf("dial %s failed; err :%s",ADDRESS,err) return }
2. 通信
2.1 服務(wù)端
- 接受信息
可以通過.Read
來讀取傳輸?shù)臄?shù)據(jù)。
var data [1024]byte var msg string reader := bufio.NewReader(os.Stdin) for { // 服務(wù)端要時刻等待傳送過來的數(shù)據(jù),所以要用for循環(huán) //接受信息 n,err := conn.Read(data[:]) if err == io.EOF{ break } if err != nil { fmt.Printf("read from conn failed,err:%s",err) return } fmt.Println("Access Info : ",string(data[:n])) } defer conn.Close()
2.2 客戶端
- 發(fā)送信息
同樣可以通過.Write
在傳輸連接中傳輸數(shù)據(jù)。
for{ // 讓客戶的能一直發(fā)送信息,所以就需要一個for循環(huán),保持連接 fmt.Print("請輸入:") msg,_ = reader.ReadString('\n') msg = strings.TrimSpace(msg) if msg == "exit" { break } _, _ = conn.Write([]byte(msg)) }
3. 回復(fù)
當(dāng)服務(wù)端收到信息之后,應(yīng)該返回信息給客戶端。表示已經(jīng)收到了數(shù)據(jù)。
3.1 服務(wù)端
服務(wù)端回復(fù)信息
//回復(fù)信息 fmt.Print("回復(fù)信息:") msg,_ = reader.ReadString('\n') msg = strings.TrimSpace(msg) if msg == "exit" { break } _ ,_ = conn.Write([]byte(msg))
3.2 客戶端
客戶端收到信息
// 接受信息 n,err:=conn.Read(data[:]) if err == io.EOF { break } if err != nil { fmt.Println("read from conn failed, err :",err) return } fmt.Println("收到的回復(fù):",string(data[:n]))
以上就是Go語言TCP從原理到代碼實現(xiàn)詳解的詳細內(nèi)容,更多關(guān)于Go TCP原理代碼的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang中range在slice和map遍歷中的注意事項
今天小編就為大家分享一篇關(guān)于golang中range在slice和map遍歷中的注意事項,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-04-04Golang在Mac、Linux、Windows下如何交叉編譯的實現(xiàn)
這篇文章主要介紹了Golang在Mac、Linux、Windows下如何交叉編譯的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03GO語言協(xié)程互斥鎖Mutex和讀寫鎖RWMutex用法實例詳解
這篇文章主要介紹了GO語言協(xié)程互斥鎖Mutex和讀寫鎖RWMutex用法詳解,需要的朋友可以參考下2022-04-04Golang中crypto/ecdsa庫實現(xiàn)數(shù)字簽名和驗證
本文主要介紹了Golang中crypto/ecdsa庫實現(xiàn)數(shù)字簽名和驗證,將從ECDSA的基本原理出發(fā),詳細解析如何在Go語言中實現(xiàn)數(shù)字簽名和驗證,具有一定的參考價值,感興趣的可以了解一下2024-02-02Golang并發(fā)繞不開的重要組件之Channel詳解
Channel是一個提供可接收和發(fā)送特定類型值的用于并發(fā)函數(shù)通信的數(shù)據(jù)類型,也是Golang并發(fā)繞不開的重要組件之一,本文就來和大家深入聊聊Channel的相關(guān)知識吧2023-06-06Golang實現(xiàn)WebSocket服務(wù)的項目實踐
本文介紹如何使用Golang實現(xiàn)實時后端WebSocket服務(wù),首先使用Gin框架搭建http服務(wù),然后使用gorilla/websocket庫實現(xiàn)簡單后端WebSocket服務(wù),具有一定的參考價值,感興趣的可以了解一下2023-05-05