Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn)
網(wǎng)絡(luò)編程介紹
網(wǎng)絡(luò)編程介紹
- 網(wǎng)絡(luò)編程是指通過計(jì)算機(jī)網(wǎng)絡(luò)實(shí)現(xiàn)程序間通信的一種編程技術(shù),涉及到在不同計(jì)算機(jī)之間建立連接、傳輸數(shù)據(jù)和協(xié)議解析等操作。
- 套接字(Socket)編程是網(wǎng)絡(luò)編程的一種實(shí)現(xiàn)方式,其提供了一種機(jī)制,使得應(yīng)用程序能夠通過網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)傳輸和通信。
- Go中的net包是標(biāo)準(zhǔn)庫中提供的網(wǎng)絡(luò)編程包,是基于套接字編程的一種實(shí)現(xiàn)方式,提供了對TCP、UDP、IP、ICMP、Unix域套接字等常見網(wǎng)絡(luò)協(xié)議的支持,通過net包可以完成創(chuàng)建套接字、建立連接、發(fā)送和接收數(shù)據(jù)等操作,實(shí)現(xiàn)網(wǎng)絡(luò)通信。
TCP網(wǎng)絡(luò)編程
服務(wù)器監(jiān)聽
服務(wù)器監(jiān)聽
在Go的net包中,Listen函數(shù)用于創(chuàng)建并返回一個網(wǎng)絡(luò)監(jiān)聽器(Listener),以監(jiān)聽指定網(wǎng)絡(luò)地址和端口上的連接請求。該函數(shù)的函數(shù)原型如下:
func Listen(network, address string) (Listener, error)
參數(shù)說明:
- network:用于指定網(wǎng)絡(luò)類型,其值必須是"tcp", “tcp4”, “tcp6”, “unix"或"unixpacket”。
- address:用于指定需要被監(jiān)聽的IP地址和端口號,格式為"host:port"。
返回值說明:
- 第一個返回值:表示創(chuàng)建的網(wǎng)絡(luò)監(jiān)聽器。
- 第二個返回值:如果創(chuàng)建網(wǎng)絡(luò)監(jiān)聽器過程中出錯,將返回非nil的錯誤值。
通過Listen函數(shù)創(chuàng)建得到的網(wǎng)絡(luò)監(jiān)聽器是Listener類型的,該類型是一個接口類型,其定義如下:
type Listener interface { // Accept waits for and returns the next connection to the listener. Accept() (Conn, error) // Close closes the listener. // Any blocked Accept operations will be unblocked and return errors. Close() error // Addr returns the listener's network address. Addr() Addr }
Listener接口中各方法說明:
- Accept方法:從底層獲取下一個已經(jīng)建立好的連接給監(jiān)聽器。
- Close方法:關(guān)閉監(jiān)聽器。
- Addr方法:返回監(jiān)聽器對應(yīng)的網(wǎng)絡(luò)地址(由IP地址和端口號組成)。
當(dāng)使用Listen函數(shù)創(chuàng)建TCP類型的監(jiān)聽器時,其返回的監(jiān)聽器底層具體的類型是TCPListener,其定義如下:
type TCPListener struct { fd *netFD lc ListenConfig }
TCPListener結(jié)構(gòu)體各字段說明:
- fd:對底層網(wǎng)絡(luò)文件描述符的封裝,提供了對網(wǎng)絡(luò)連接的讀寫和控制操作。
- lc:用于配置監(jiān)聽器創(chuàng)建的行為,比如設(shè)置監(jiān)聽地址、控制網(wǎng)絡(luò)參數(shù)等。
TCPListener結(jié)構(gòu)體中的fd字段是netFD類型的,其定義如下:
type netFD struct { pfd poll.FD // immutable until Close family int sotype int isConnected bool // handshake completed or use of association with peer net string laddr Addr raddr Addr }
netFD結(jié)構(gòu)體各字段說明:
- pfd:用于與底層的操作系統(tǒng)文件描述符進(jìn)行交互。
- family:表示套接字的協(xié)議家族,比如IPv4或IPv6。
- sotype:表示套接字的類型,比如TCP或UDP
- isConnected:表示連接是否已完成握手或與對方建立關(guān)聯(lián)。
- net:表示網(wǎng)絡(luò)協(xié)議,比如"tcp"或"udp"。
- laddr:表示本地網(wǎng)絡(luò)連接的地址。
- raddr:表示遠(yuǎn)程網(wǎng)絡(luò)連接的地址。
服務(wù)器監(jiān)聽示例
服務(wù)器監(jiān)聽示例如下:
package main import ( "fmt" "net" ) func main() { // 服務(wù)器監(jiān)聽 listen, err := net.Listen("tcp", "0.0.0.0:8081") if err != nil { fmt.Printf("listen error, err = %v\n", err) return } defer listen.Close() fmt.Println("listen success...") }
說明一下:
- 服務(wù)器在創(chuàng)建監(jiān)聽套接字時,將其綁定到
0.0.0.0
(通常表示為INADDR_ANY)地址,這樣服務(wù)器就可以同時監(jiān)聽和接受來自不同網(wǎng)絡(luò)接口的連接請求,而不需要為每個接口分別創(chuàng)建監(jiān)聽套接字。 - 為了避免網(wǎng)絡(luò)文件描述符資源泄露,在創(chuàng)建監(jiān)聽器后及時利用defer機(jī)制關(guān)閉監(jiān)聽器。監(jiān)聽器被關(guān)閉后會停止監(jiān)聽新的連接請求,并且任何被阻塞的Accept操作都會被解除阻塞并返回錯誤。
客戶端連接服務(wù)器
客戶端連接服務(wù)器
在Go的net包中,Dial函數(shù)用于客戶端應(yīng)用程序與遠(yuǎn)程服務(wù)器建立連接。該函數(shù)的函數(shù)原型如下:
func Dial(network, address string) (Conn, error)
參數(shù)說明:
- network:用于指定網(wǎng)絡(luò)協(xié)議,比如"tcp", "udp"等。
- address:用于指定連接的目標(biāo)地址。
返回值說明:
- 第一個返回值:表示建立的網(wǎng)絡(luò)連接。
- 第二個返回值:如果建立網(wǎng)絡(luò)連接過程中出錯,將返回非nil的錯誤值。
通過Dial函數(shù)建立得到的連接Conn類型的,該類型是一個接口類型,其定義如下:
type Conn interface { // Read reads data from the connection. Read(b []byte) (n int, err error) // Write writes data to the connection. Write(b []byte) (n int, err error) // Close closes the connection. // Any blocked Read or Write operations will be unblocked and return errors. Close() error // LocalAddr returns the local network address, if known. LocalAddr() Addr // RemoteAddr returns the remote network address, if known. RemoteAddr() Addr // SetDeadline sets the read and write deadlines associated // with the connection. It is equivalent to calling both // SetReadDeadline and SetWriteDeadline. SetDeadline(t time.Time) error // SetReadDeadline sets the deadline for future Read calls // and any currently-blocked Read call. SetReadDeadline(t time.Time) error // SetWriteDeadline sets the deadline for future Write calls // and any currently-blocked Write call. SetWriteDeadline(t time.Time) error }
Conn接口中各方法說明:
- Read方法:從連接中讀取數(shù)據(jù)。
- Write方法:向連接中寫入數(shù)據(jù)。
- Close方法:關(guān)閉連接。
- LocalAddr方法:返回連接對應(yīng)的本地網(wǎng)絡(luò)地址(由IP地址和端口號組成)。
- RemoteAddr方法:返回連接對應(yīng)的遠(yuǎn)程網(wǎng)絡(luò)地址(由IP地址和端口號組成)。
- SetDeadline方法:設(shè)置連接讀取和寫入的截止時間。
- SetReadDeadline方法:設(shè)置連接讀取的截止時間。
- SetWriteDeadline方法:設(shè)置連接寫入的截止時間。
當(dāng)使用Dial函數(shù)與TCP服務(wù)器建立連接時,其返回的網(wǎng)絡(luò)連接底層具體的類型是TCPConn,其定義如下:
type TCPConn struct { conn }
TCPConn結(jié)構(gòu)體中僅嵌套了一個conn類型的匿名結(jié)構(gòu)體,其定義如下:
type conn struct { fd *netFD }
可以看到,conn結(jié)構(gòu)體中的fd字段與TCPListener結(jié)構(gòu)體中的fd字段的類型相同,它們都是對底層網(wǎng)絡(luò)文件描述符的封裝,提供了對網(wǎng)絡(luò)連接的讀寫和控制操作。
客戶端連接服務(wù)器示例
客戶端連接服務(wù)器示例如下:
package main import ( "fmt" "net" ) func main() { // 客戶端連接服務(wù)器 conn, err := net.Dial("tcp", "127.0.0.1:8081") if err != nil { fmt.Printf("connect server error, err = %v\n", err) return } defer conn.Close() fmt.Println("connect server success...") }
說明一下:
- 當(dāng)客戶端連接TCP服務(wù)器時,服務(wù)器必須處于監(jiān)聽狀態(tài)。
- 為了避免網(wǎng)絡(luò)文件描述符資源泄露,客戶端在與服務(wù)器建立連接后及時利用defer機(jī)制關(guān)閉連接。
服務(wù)端獲取連接
服務(wù)端獲取連接
在創(chuàng)建TCP網(wǎng)絡(luò)監(jiān)聽器后,調(diào)用Listener接口的Accept方法,本質(zhì)調(diào)用的是TCPListener的Accept方法,該方法用于從底層獲取下一個已經(jīng)建立好的連接給監(jiān)聽器,如果底層沒有建立好的連接則會進(jìn)行阻塞等待。該方法的原型如下:
func (l *TCPListener) Accept() (Conn, error)
返回值說明:
- 第一個返回值:表示獲取到的與客戶端建立好的連接。
- 第二個返回值:如果在獲取連接過程中出錯,將返回非nil的錯誤值。
服務(wù)端獲取連接示例
服務(wù)端獲取連接示例如下:
package main import ( "fmt" "net" ) func process(conn net.Conn) { defer conn.Close() fmt.Printf("handle a link %v...\n", conn.RemoteAddr()) } func main() { // 服務(wù)器監(jiān)聽 listen, err := net.Listen("tcp", "0.0.0.0:8081") if err != nil { fmt.Printf("listen error, err = %v\n", err) return } defer listen.Close() fmt.Println("listen success...") for { fmt.Println("waiting client connect...") // 服務(wù)端獲取連接 conn, err := listen.Accept() if err != nil { fmt.Printf("accept error, err = %v\n", err) continue } fmt.Printf("get a link from %v...\n", conn.RemoteAddr()) // 開啟新協(xié)程為客戶端提供服務(wù) go process(conn) } }
說明一下:
- 網(wǎng)絡(luò)監(jiān)聽器的任務(wù)就是不斷調(diào)用Accept方法,從底層獲取已經(jīng)建立好的連接并為其提供服務(wù),通常在獲取到一個連接后會開啟一個新協(xié)程為其提供服務(wù),而主協(xié)程則繼續(xù)調(diào)用Accept方法獲取新的連接。
- 為了讓新協(xié)程能夠獲取需要被處理的連接,需要將對應(yīng)的連接通過參數(shù)傳遞的方式,傳遞給協(xié)程對應(yīng)的處理函數(shù)。此外,為了避免網(wǎng)絡(luò)文件描述符資源泄露,需要在處理函數(shù)中利用defer機(jī)制關(guān)閉連接,保證連接處理完畢后能夠及時關(guān)閉連接。
向連接中寫入數(shù)據(jù)
向連接中寫入數(shù)據(jù)
在創(chuàng)建TCP連接后,調(diào)用Conn接口的Write方法,本質(zhì)調(diào)用的是TCPConn的Write方法,該方法用于向連接中寫入數(shù)據(jù)。該方法的原型如下:
func (c *TCPConn) Write(b []byte) (int, error)
參數(shù)說明:
- b:表示要寫入連接的數(shù)據(jù)。
返回值說明:
- 第一個返回值:表示實(shí)際寫入的字節(jié)數(shù)。
- 第二個返回值:如果在寫入數(shù)據(jù)過程中出錯,將返回非nil的錯誤值。
從連接中讀取數(shù)據(jù)
從連接中讀取數(shù)據(jù)
在創(chuàng)建TCP連接后,調(diào)用Conn接口的Read方法,本質(zhì)調(diào)用的是TCPConn的Read方法,該方法用于從連接中讀取數(shù)據(jù)。該方法的原型如下:
func (c *TCPConn) Read(b []byte) (int, error)
參數(shù)說明:
- b:輸出型參數(shù),用于存儲讀取到的數(shù)據(jù)。
返回值說明:
- 第一個返回值:表示實(shí)際讀取的字節(jié)數(shù)。
- 第二個返回值:如果在讀取數(shù)據(jù)過程中出錯,將返回非nil的錯誤值。
關(guān)閉連接/監(jiān)聽器
關(guān)閉連接/監(jiān)聽器
為了避免網(wǎng)絡(luò)文件描述符泄露,TCP網(wǎng)絡(luò)監(jiān)聽器和TCP連接在使用完畢后都需要及時將其關(guān)閉,對應(yīng)調(diào)用的分別是TCPListener和TCPConn的Close方法,這兩個方法的原型如下:
func (l *TCPListener) Close() error func (c *TCPConn) Close() error
返回值說明:
- 如果在關(guān)閉監(jiān)聽器或關(guān)閉連接過程中出錯,將返回非nil的錯誤值。
簡易的TCP回聲服務(wù)器
效果展示
效果展示
為了演示使用net包實(shí)現(xiàn)網(wǎng)絡(luò)通信,下面實(shí)現(xiàn)了一個簡易的TCP回聲服務(wù)器,其功能如下:
- 服務(wù)端能夠同時處理多個客戶端的連接請求,在為每個客戶端提供服務(wù)時,能夠?qū)⒏鱾€客戶端發(fā)來的數(shù)據(jù)顯示在服務(wù)端,同時將客戶端發(fā)來的數(shù)據(jù)再發(fā)回給客戶端。
- 客戶端在連接到服務(wù)器后,能夠不斷從控制臺讀取用戶輸入的數(shù)據(jù)發(fā)送給服務(wù)端,并將服務(wù)端發(fā)來的數(shù)據(jù)顯示在客戶端,用戶在控制臺輸入exit能夠退出客戶端。
最終效果如下:
服務(wù)端處理邏輯
服務(wù)端處理邏輯
服務(wù)端處理邏輯如下:
- 主協(xié)程調(diào)用Listen函數(shù)完成服務(wù)器的監(jiān)聽后,通過監(jiān)聽器不斷調(diào)用Accept方法從底層獲取已經(jīng)建立好的連接,并為每一個獲取到的連接創(chuàng)建一個新協(xié)程為其提供服務(wù),而主協(xié)程則繼續(xù)獲取新的連接。
- 每個新協(xié)程在為其對應(yīng)的連接提供服務(wù)時,通過調(diào)用連接的Read方法不斷讀取客戶端發(fā)來的數(shù)據(jù),將數(shù)據(jù)其顯示在服務(wù)端,同時通過調(diào)用連接的Write方法將客戶端發(fā)來的數(shù)據(jù)再發(fā)回給客戶端。
- 每個新協(xié)程在為其對應(yīng)的連接提供服務(wù)的過程中,如果從連接中讀取數(shù)據(jù)或向連接中寫入數(shù)據(jù)時出錯,或是客戶端退出,則通過調(diào)用連接的Close方法將對應(yīng)的連接關(guān)閉。
服務(wù)端代碼如下:
package main import ( "fmt" "io" "net" ) func process(conn net.Conn) { defer conn.Close() data := make([]byte, 1024) for { // 1、讀取客戶端發(fā)來的數(shù)據(jù) n, err := conn.Read(data) if err != nil { if err == io.EOF { fmt.Printf("client %v quit\n", conn.RemoteAddr()) } else { fmt.Printf("read client message error, err = %v\n", err) } return } fmt.Printf("client message[%v]: %v\n", conn.RemoteAddr(), string(data[:n])) // 2、發(fā)送數(shù)據(jù)給客戶端 len, err := conn.Write(data[:n]) if err != nil || len != n { fmt.Printf("send back message error, err = %v\n", err) return } } } func main() { // 服務(wù)器監(jiān)聽 listen, err := net.Listen("tcp", "0.0.0.0:8081") if err != nil { fmt.Printf("listen error, err = %v\n", err) return } defer listen.Close() fmt.Println("listen success...") for { fmt.Println("waiting client connect...") // 服務(wù)端獲取連接 conn, err := listen.Accept() if err != nil { fmt.Printf("accept error, err = %v\n", err) continue } fmt.Printf("get a link from %v...\n", conn.RemoteAddr()) // 開啟新協(xié)程為客戶端提供服務(wù) go process(conn) } }
說明一下:
- 當(dāng)服務(wù)端從某個連接中讀取數(shù)據(jù)時,如果該連接對應(yīng)的客戶端已經(jīng)將連接關(guān)閉,那么服務(wù)端的讀操作將會返回io.EOF錯誤。
客戶端處理邏輯
客戶端處理邏輯
客戶端處理邏輯如下:
- 客戶端在調(diào)用Dial函數(shù)與服務(wù)端建立連接后,不斷讀取用戶的輸入,通過調(diào)用連接Write方法將用戶輸入的數(shù)據(jù)發(fā)送給服務(wù)端,然后通過調(diào)用連接的Read方法讀取服務(wù)端發(fā)來的數(shù)據(jù)并顯示在客戶端,如果用戶輸入exit則調(diào)用連接的Close方法將連接關(guān)閉。
客戶端代碼如下:
package main import ( "bufio" "fmt" "net" "os" ) func main() { // 客戶端連接服務(wù)器 conn, err := net.Dial("tcp", "127.0.0.1:8081") if err != nil { fmt.Printf("connect server error, err = %v\n", err) return } defer conn.Close() fmt.Println("connect server success...") reader := bufio.NewReader(os.Stdin) data := make([]byte, 1024) for { // 1、讀取用戶輸入 str, err := reader.ReadString('\n') if err != nil { fmt.Printf("read input error, err = %v\n", err) continue } str = str[:len(str)-2] // 去掉\r\n if str == "exit" { fmt.Printf("exit success...") break } // 2、發(fā)送數(shù)據(jù)給服務(wù)端 n, err := conn.Write([]byte(str)) if err != nil || n != len(str) { fmt.Printf("send message error, err = %v\n", err) continue } fmt.Printf("send %d byte message to server...\n", n) // 3、讀取服務(wù)端發(fā)來的數(shù)據(jù) n, err = conn.Read(data) if err != nil { fmt.Printf("read message error, err = %v\n", err) continue } fmt.Printf("server message: %v\n", string(data[:n])) } }
說明一下:
- Stdin、Stdout和Stderr是os包中的全局變量,分別表示標(biāo)準(zhǔn)輸入流、標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯誤流。
- 客戶端在讀取用戶輸入時,通過bufio包中的Reader,以帶緩沖的方式每次從標(biāo)準(zhǔn)輸入流中讀取一行數(shù)據(jù)。
- Windows系統(tǒng)中通常使用
\r\n
作為換行符,因此客戶端在每次讀取一行用戶輸入的數(shù)據(jù)后需要將末尾的兩個字符去掉。
到此這篇關(guān)于Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang TCP網(wǎng)絡(luò)編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言fmt.Sprintf格式化輸出的語法與實(shí)例
Go 可以使用 fmt.Sprintf 來格式化字符串,下面這篇文章主要給大家介紹了關(guān)于Go語言fmt.Sprintf格式化輸出的語法與實(shí)例,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07Go語言CSP并發(fā)模型實(shí)現(xiàn)MPG
這篇文章主要為大家介紹了Go語言CSP并發(fā)模型實(shí)現(xiàn)MPG圖文詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Golang構(gòu)建WebSocket服務(wù)器和客戶端的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何使用Go語言構(gòu)建WebSocket服務(wù)器和客戶端,以實(shí)現(xiàn)雙向通信,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2023-11-11從淺入深帶你掌握Golang數(shù)據(jù)結(jié)構(gòu)map
在?Go?語言中,map?是一種非常常見的數(shù)據(jù)類型,它可以用于快速地檢索數(shù)據(jù)。本篇文章將介紹?Go?語言中的?map,包括?map?的定義、初始化、操作和優(yōu)化,需要的可以參考一下2023-04-04Go中調(diào)用JS代碼(otto)的實(shí)現(xiàn)示例
Otto是一個用Go語言實(shí)現(xiàn)的JavaScript解釋器,可用于執(zhí)行和操作JavaScript代碼,適合在Go項(xiàng)目中執(zhí)行簡單的JS腳本,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-10-10使用go實(shí)現(xiàn)刪除sql里面的注釋和字符串功能(demo)
這篇文章主要介紹了使用go實(shí)現(xiàn)刪除sql里面的注釋和字符串功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11