Go語言如何實現TCP通信詳解

前言

TCP協議為傳輸控制協議,TCP協議有以下幾個特點:

1. TCP是面向連接的傳輸層協議;

2. 每條TCP連接隻能有兩個端點,每條TCP連接是點到點的通信;

3. TCP提供可靠的交付服務,保證傳送的數據無差錯,不丟失,不重要且有序;

4. TCP提供全雙工通信,允許雙方在任何時候都能發送數據,為此TCP連接的兩端都設有發送緩存和接收緩存,用來臨時存放雙向通信的數據;

5. TCP是面向字節流的;

發送緩存用來暫存以下數據:

① 發送應用程序傳送給發送方TCP準備發送的數據;

② TCP已發送但尚未收到確認的數據;

接收緩存用來暫存以下數據:

① 按序到達但尚未被接收應用程序讀取的數據;

② 不按序到達的數據;

因為是面向連接的協議,數據像水流一樣傳輸,會存在黏包問題

TCP連接

一個TCP服務端可以同時連接很多個客戶端,Go語言可以使用go關鍵字開啟goroutine,每建立一個連接就創建一個goroutine,這樣可以並發執行每一個創建的連接,tcp服務端主要的處理流程有:1. 監聽端口;2. 接收客戶端請求創建tcp連接;3. 使用go關鍵字開啟goroutine處理每一個建立的連接,收發數據;4. 關閉連接;tcp客戶端主要的處理流程有:1. 建立與服務端的連接;2. 收發數據;3. 關閉連接;關於tcp通信我們一般會使用到bufio,net,strings,os包,其中bufio包主要用來做輸入輸出數據的緩存,bufio.NewReader()函數可以傳遞os.Stdin類型(任何實現瞭io.Reader接口中的read()方法都可以作為參數傳遞進來,通常是傳遞一個實現瞭io.Reader接口中的read()方法的結構體或者是結構體指針),os.Stdin為標準輸入,我們可以接受讀取從控制臺輸入的數據,os.NewReader()函數返回的是一個新的帶有4096 byte大小緩沖區的Reader結構體指針類型,通過Reader結構體指針類型我們就可以調用很多的方法,比如可以調用ReadString(delim byte) (string,error)方法,ReadString方法可以一直從標準輸入中讀取數據,直到遇到指定的終止符號delim,並且讀取的內容會包含當前的delim,所以bufio包主要用來對輸入和輸出的數據進行緩沖;net包主要是連接的監聽、創建,以及連接數據的讀取和寫入(例如tcp,udp等網絡編程)等相關工作,tcp服務端中可以使用一個process()函數傳遞一個net.Conn類型也即*net.TCPConn類型的變量conn(TCPConn結構體實現瞭Conn接口),*TCPConn類型那麼就可以調用很多的方法,因為我們需要從tcp連接中讀取客戶端或者是服務端發送的數據,所以可以調用*TCPConn類型的read()方法讀取tcp連接的數據,然後輸出數據即可,服務端可以使用net.Listen()函數監聽連接,使用Accept()方法建立tcp連接,客戶端則可以使用net.Dial()連接創建的tcp連接,使用conn接口的實現*TCPConn(Dial()方法的返回值就是*TCPConn類型)調用conn的read()方法將讀取tcp連接的內容,write()方法將數據寫入到tcp連接中;strings包主要是對讀取到的數據的字符串形式進行處理,比如去除掉字符串的一些符號等等。

下面的代碼需要先運行服務端的代碼,再運行客戶端的代碼:

TCP服務端

package main
 
import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)
 
// TCP 服務端
func process(conn net.Conn) {
	// 函數執行完之後關閉連接
	defer conn.Close()
	// 輸出主函數傳遞的conn可以發現屬於*TCPConn類型, *TCPConn類型那麼就可以調用*TCPConn相關類型的方法, 其中可以調用read()方法讀取tcp連接中的數據
	fmt.Printf("服務端: %T\n", conn)
	for {
		var buf [128]byte
		// 將tcp連接讀取到的數據讀取到byte數組中, 返回讀取到的byte的數目
		n, err := conn.Read(buf[:])
		if err != nil {
			// 從客戶端讀取數據的過程中發生錯誤
			fmt.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("服務端收到客戶端發來的數據:", recvStr)
		// 由於是tcp連接所以雙方都可以發送數據, 下面接收服務端發送的數據這樣客戶端也可以收到對應的數據
		inputReader := bufio.NewReader(os.Stdin)
		s, _ := inputReader.ReadString('\n')
		t := strings.Trim(s, "\r\n")
		// 向當前建立的tcp連接發送數據, 客戶端就可以收到服務端發送的數據
		conn.Write([]byte(t))
	}
}
 
func main() {
	// 監聽當前的tcp連接
	listen, err := net.Listen("tcp", "127.0.0.1:20000")
	fmt.Printf("服務端: %T=====\n", listen)
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	for {
		conn, err := listen.Accept() // 建立連接
		fmt.Println("當前建立瞭tcp連接")
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		// 對於每一個建立的tcp連接使用go關鍵字開啟一個goroutine處理
		go process(conn) 
	}
}

TCP客戶端

package main
 
import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)
 
func main() {
	// 連接到服務端建立的tcp連接
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	// 輸出當前建Dial函數的返回值類型, 屬於*net.TCPConn類型
	fmt.Printf("客戶端: %T\n", conn)
	if err != nil {
		// 連接的時候出現錯誤
		fmt.Println("err :", err)
		return
	}
	// 當函數返回的時候關閉連接
	defer conn.Close() 
	// 獲取一個標準輸入的*Reader結構體指針類型的變量
	inputReader := bufio.NewReader(os.Stdin)
	for {
		// 調用*Reader結構體指針類型的讀取方法
		input, _ := inputReader.ReadString('\n') // 讀取用戶輸入
		// 去除掉\r \n符號
		inputInfo := strings.Trim(input, "\r\n")
		// 判斷輸入的是否是Q, 如果是Q則退出
		if strings.ToUpper(inputInfo) == "Q" { // 如果輸入q就退出
			return
		}
		_, err = conn.Write([]byte(inputInfo)) // 發送數據
		if err != nil {
			return
		}
		buf := [512]byte{}
		// 讀取服務端發送的數據
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("recv failed, err:", err)
			return
		}
		fmt.Println("客戶端接收服務端發送的數據: ", string(buf[:n]))
	}
}

總結

到此這篇關於Go語言如何實現TCP通信的文章就介紹到這瞭,更多相關Go語言TCP通信內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: