首頁 > 軟體

Go語言TCP從原理到程式碼實現詳解

2022-08-22 14:01:30

引言

基於net包的小應用

完整程式碼已經上傳到github GitHub-TCP

歡迎starissue

TCP介紹

特點

  • 面向連線的運輸層協定。在應用程式在使用TCP協定之前,必須先建立TCP連線。在傳送資料完畢後,必須釋放已經建立的TCP連線。
  • 每一條TCP連線只能有兩個端點,每一條TCP連線只能是對等的。
  • TCP提供可靠交付的服務。 通過TCP連線傳送的資料,無差錯,不丟失,不重複,並且按序到達。
  • TCP提供全雙工通訊。 TCP允許通訊雙方的應用程序在任何時候都能傳送資料。
  • 面向位元組流。 TCP的流是指流入到程序或從程序流出的位元組序列。雖然應用程式和TCP的互動式一次一個資料塊,但TCP把應用程式交下來的資料僅僅看成是一連串的無結構的位元組流。

圖解

  • TCP結構

  • TCP連線

TCP 連線建立,三次握手

傳輸控制塊TCB:儲存了每一個連線中的一些重要資訊。比如TCP連線表,指向傳送和接收緩衝的指標,指向重傳佇列的指標,當前的傳送和接收序列等等。

假設主機A是TCP客戶程式,B是TCP伺服器程式。最初兩端的TCP程序都是處於CLOSED關閉狀態,使用者端A開啟連結,伺服器端被開啟連結。一開始B的TCP伺服器程序先建立傳輸控制塊TCB,準備接受客戶程序的連結請求,然後伺服器程序就處於LISTEN收聽狀態,等待A的連線請求。

  • 然後A的程序首先建立傳輸控制模組TCB。向B發出連線請求報文段,這是首部當中的同步位SYN=1,同時選擇一個初始序號seq=x。TCP規定,SYN報文段(即SYN=1的報文段)不能寫資料,但要消耗掉一個序號。這時候A就進入了同步已傳送的狀態。
  • B收到連線請求報文段後,如果同意建立連線,則向A傳送確認,在確認報文段中把SYN位和AVK位置都置為1,確認號為ack+1,同時也為自己選擇一個初始序號y。同樣的這個報文段也是不能寫資料的,但同時要消耗掉一個序號。這時B進入了同步收到狀態。
  • A收到B的確認之後,還要向B給出確認。確認報文段的ACK置1,確認號ack=y+1,而自己的seq=x+1。ACK報文段是可以攜帶資料的,但如果不攜帶資料則不消耗序號,在這種情況下,下一個資料包文段的序號仍為seq=x+1

這時候TCP已經建立了。A進行入了已經建立連線的階段狀態。B收到確認後也進入了連線狀態。

TCP 連線釋放,四次揮手

資料傳輸完畢之後,通訊的雙方都可釋放連線。現在A和B都處於ESTABLISHED狀態。

  • A的應用程序先向TCP發出連線釋放報文段,並停止再傳送資料,主動關閉TCP連線。A把連結釋放報文段首部的終止控制位FIN置為1,其序號為seq=u,它等於前面以傳送過的資料的最後一個位元組的序號加1.這時候A進入了FIN-WAIT-1(終止等待1)狀態,等待B的確認。

注意:TCP規定,FIN報文段即使不攜帶資料,他也消耗掉一個序號!!

  • B 收到連結釋放報文段後即發出確認,確認號是ack = u + 1,而這個報文段自己的序號是v,等於B前面已傳送過的資料的最後一個位元組的序號加1.然後B就進入CLOSE-WAIT(關閉等待)狀態。TCP伺服器程序這時應通知高層應用程序,因而從A到B這個方向的連結就釋放了,這時的TCP連結處於半關閉狀態,即A已經沒有資料要傳送了,但B若傳送資料,A仍要接收,也就是說,從B到A這個方向的連線並未關閉。這個狀態可能要維持一段時間。
  • A收到來自B的確認後,就進入了FIN-WAIT-2(終止等待2)狀態滿等待B發出的連線釋放報文段。若B已經沒有要向A傳送的資料,其應用程序就通知TCP釋放連線,這時B發出的連線釋放報文段必須使FIN = 1,現假定B的序號為w(在半關閉狀態B可能又傳送了一些資料)。B還必須重複上次已傳送過的確認號ack = u + 1.這時B就進入LAST-ACK(最後確認)狀態,等待A的確認。
  • A在收到了B的連結釋放報文段後,必須對此發出確認。在確認報文段中把ACK置1,確認號ack=w+1,而自己的序號是seq=u+1(根據TCP標準,前面傳送過的FIN報文段要消耗一個序號)。然後進入到TIME-WAIT(時間等待)狀態。注意: 現在TCP連線還沒有還沒有釋放掉。必須經過時間等待計時器設定的時間2MSL後,A才能進入CLOSED狀態。

時間MSL叫做最長報文段壽命,RFC793建議設在兩分鐘。但是在現在工程來看兩分鐘太長了,所以TCP允許不同的實現可以根據具體情況使用更小的MSL值。

程式碼實現

首先建立兩個目錄,一個是client使用者端,另一個是server伺服器端。

1. 連線

1.1 伺服器端

  • 監聽連線

net 中提供了Listen方法,可以讓伺服器端進行埠監聽

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方法,讓使用者端連線伺服器端

ADDRESS := "127.0.0.1:5000"
conn,err := net.Dial("tcp",ADDRESS) // 主動與伺服器端建立連線
if err != nil {
	fmt.Printf("dial %s failed; err :%s",ADDRESS,err)
	return
}

2. 通訊

2.1 伺服器端

  • 接受資訊

可以通過.Read來讀取傳輸的資料。

	var data [1024]byte
	var msg string
	reader := bufio.NewReader(os.Stdin)
	for {   // 伺服器端要時刻等待傳送過來的資料,所以要用for迴圈
		//接受資訊
		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 使用者端

  • 傳送資訊

同樣可以通過.Write在傳輸連線中傳輸資料。

	for{ // 讓客戶的能一直傳送資訊,所以就需要一個for迴圈,保持連線
		fmt.Print("請輸入:")
		msg,_ = reader.ReadString('n')
		msg = strings.TrimSpace(msg)
		if msg == "exit" {
			break
		}
		_, _ = conn.Write([]byte(msg))
	}

3. 回覆

當伺服器端收到資訊之後,應該返回資訊給使用者端。表示已經收到了資料。

3.1 伺服器端

伺服器端回覆資訊

	//回覆資訊
	fmt.Print("回覆資訊:")
	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("收到的回覆:",string(data[:n]))

以上就是Go語言TCP從原理到程式碼實現詳解的詳細內容,更多關於Go TCP原理程式碼的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com