一篇文章帶你瞭解 Go 語言基礎之網絡編程

前言

Hi,大家好呀,我是碼農,星期八,我們身處 21 世紀,我們的世界已經在不知不覺中,就像很多網一樣在互聯互通。

互聯網是一個統稱,目前比較常用的有TCPUDP協議。

當然,還有很多其他的協議,但是本次主要講最常用的TCPUDP協議。

socker 編程

我們所學的TCPUDP,統稱爲Socker編程,也叫做套接字編程

多臺機器要實現互相通訊,其實是一個非常複雜的過程,底層從鋪設網線網線接口交換機路由器,在到規定各種協議

再到應用層QQ微信等軟件。

如果沒有一套標準,每次使用都要自己去實現,可能每個程序員都不是掉頭髮那麼簡單了!

有了Socker之後,Socker會在應用層之前,將各種繁瑣的的底層操作隱藏,我們可能只需要Socker.TCP就實現了TCP協議的通訊。

Go 語言 TCP

TCP 屬於穩定的,可靠的長連接,

既然要涉及通訊,必然有兩個終端,最起碼一個是服務端,一個是客戶端,就像我們的淘寶,我們每次打開淘寶,都要去鏈接它,當然,淘寶可不直接是TCP

服務端

在 Go 中實現服務端,並且在服務端併發很簡單,只需要將每個連接讓一個協程處理即可!

代碼

package main
import (
    "bufio"
    "fmt"
    "net"
)
func process(conn net.Conn) {
    defer conn.Close()
    for {
        reader := bufio.NewReader(conn)
        buf := make([]byte 128)
        n err := reader.Read(buf)
        if err != nil {
            fmt.Println("數據讀取失敗" err)
            return
        }
        recvStr := string(buf[:n])
        fmt.Println("客戶端發送過來的值:" recvStr)
}
}
func main() {
    lister err := net.Listen("tcp" "0.0.0.0:8008")
    if err != nil {
        fmt.Println("連接失敗" err)
}
    for {
        fmt.Println("等待建立連接,此時會阻塞住")
        conn err := lister.Accept() //等待建立連接
        fmt.Println("連接建立成功,繼續")
        if err != nil {
            fmt.Println("建立連接失敗" err)
            //繼續監聽下次鏈接
            continue
        }
        go process(conn)
}
}

客戶端

客戶端就很簡單了,相對來說是不需要併發的,只需要連接就行。

代碼

package main
import (
    "bufio"
    "fmt"
    "net"
    "os"
)
//客戶端
func main() {
    conn err := net.Dial("tcp" "192.168.10.148:8008")
    if err != nil {
        fmt.Println("連接服務器失敗"err)
}
    defer conn.Close()
    inputReader:=bufio.NewReader(os.Stdin)
    for{
        fmt.Println(":")
        input_:=inputReader.ReadString('\n')
        _ err = conn.Write([]byte(input))
        if err != nil {
            fmt.Println("發送成功")
        }
}
}

執行結果

就這樣,我們實現了服務端併發的處理所有客戶端的請求。

粘包


我們先看一下什麼是粘包。

服務端

package main
import (
    "bufio"
    "fmt"
    "io"
    "net"
)
func process(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    buf := make([]byte 1024)
    for {
        n err := reader.Read(buf)
        //讀完了
        if err == io.EOF {
            fmt.Println("讀完了")
            break
        }
        //讀錯了
        if err != nil {
            fmt.Println("數據讀取失敗" err)
            return
        }
        recvStr := string(buf[:n])
        fmt.Println("客戶端發送過來的值:" recvStr)
}
}
func main() {
    lister err := net.Listen("tcp" "0.0.0.0:8008")
    if err != nil {
        fmt.Println("連接失敗" err)
        return
}
    defer lister.Close()
    for {
        fmt.Println("等待建立連接,此時會阻塞住")
        conn err := lister.Accept() //等待建立連接
        fmt.Println("連接建立成功,繼續")
        if err != nil {
            fmt.Println("建立連接失敗" err)
            //繼續監聽下次鏈接
            continue
        }
        go process(conn)
}
}

客戶端

package main
import (
    "fmt"
    "net"
)
//客戶端
func main() {
    conn err := net.Dial("tcp" "192.168.10.148:8008")
    if err != nil {
        fmt.Println("連接服務器失敗" err)
}
    defer conn.Close()
    for i := 0; i < 10; i++ {
        sendStr := "hello world ads asdf asd fads fadsf ads ads asd asd ads "
        conn.Write([]byte(sendStr))
        time.Sleep(time.Second)
}
}

注意: 18 行代碼睡眠了 1s

執行結果

如果我註釋了第 18 行代碼

執行結果

直接都淦到一行了,what?這是啥情況,不應該跟原來一樣嗎???

每發送一個值,那邊就接收一下,這怎麼整到一塊了!!!

原因

主要原因是因爲我們是應用層軟件,是跑在操作系統之上的軟件,當我們向服務器發送一個數據時,是調用操作系統相關接口發送的,操作系統再經過各種複雜的操作,發送到對方機器

但是操作系統有一個發送數據緩衝區,默認情況如果緩衝區是有大小的,如果緩衝區沒滿,是不會發送數據的,所以上述客戶端在發送數據時,系統的緩衝區都沒滿,一直壓在了操作系統的緩衝區中,最後發現沒數據了,才一次都發送到服務端

但是爲什麼sleep(1)又管用了呢? 這是因爲緩衝區不止一個程序在用,1s 的時間足夠其他程序將緩衝區打滿,然後各自發各自的數據,這也是爲什麼第一次操作沒問題,第二次有問題,因爲第二次全部都是我們客戶端打滿的

解決粘包


工具函數

我們將解包封包的函數封裝一下

socker_sitck/stick.go

package socker_stick
import (
    "bufio"
    "bytes"
    "encoding/binary"
    "fmt"
)
//Encode 將消息編碼
func Encode(message string) ([]byte error) {
    length := int32(len(message))
    var pkg = new(bytes.Buffer)
    //寫入消息頭
    err := binary.Write(pkg binary.LittleEndian length)
    if err != nil {
        fmt.Println("寫入消息頭失敗" err)
        return nil err
}
    //寫入消息實體
    err = binary.Write(pkg binary.LittleEndian []byte(message))
    if err != nil {
        fmt.Println("寫入消息實體失敗" err)
        return nil err
}
    return pkg.Bytes() nil
}
//Decode解碼消息
func Decode(reader *bufio.Reader) (string error) {
    //讀取信息長度
    lengthByte _ := reader.Peek(4)
    lengthBuff := bytes.NewBuffer(lengthByte)
    var length int32
    err := binary.Read(lengthBuff binary.LittleEndian &length)
    if err != nil {
        return "" err
}
    //BuffRead 返回緩衝區現有的可讀的字節數
    if int32(reader.Buffered()) < length+4 {
        return "" err
}
    pack := make([]byte int(4+length))
    _ err = reader.Read(pack)
    if err != nil {
        return "" err
}
    return string(pack[4:]) nil
}

服務端

package main
import (
    "a3_course/socker_stick"
    "bufio"
    "fmt"
    "io"
    "net"
)
func process(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    for {
        msg err := socker_stick.Decode(reader)
        //讀完了
        if err == io.EOF {
            fmt.Println("讀完了")
            break
        }
        //讀錯了
        if err != nil {
            fmt.Println("數據讀取失敗" err)
            return
        }
        fmt.Println("客戶端發送過來的值:" msg)
}
}
func main() {
    lister err := net.Listen("tcp" "0.0.0.0:8008")
    if err != nil {
        fmt.Println("連接失敗" err)
        return
}
    defer lister.Close()
    for {
        fmt.Println("等待建立連接,此時會阻塞住")
        conn err := lister.Accept() //等待建立連接
        fmt.Println("連接建立成功,繼續")
        if err != nil {
            fmt.Println("建立連接失敗" err)
            //繼續監聽下次鏈接
            continue
        }
        go process(conn)
}
}

客戶端

package main
import (
    "a3_course/socker_stick"
    "fmt"
    "net"
)
//客戶端
func main() {
    conn err := net.Dial("tcp" "192.168.10.148:8008")
    if err != nil {
        fmt.Println("連接服務器失敗" err)
}
    defer conn.Close()
    for i := 0; i < 10; i++ {
        sendStr := "hello world ads asdf asd fads fadsf ads ads asd asd ads "
        data err := socker_stick.Encode(sendStr)
        if err != nil {
            fmt.Println("編碼失敗"err)
            return
        }
        conn.Write(data)
        //time.Sleep(time.Second)
}
}

執行結果

這次真的不管執行幾次,都是這樣的結果

對了,只有TCP纔有粘包

Go 語言 UDP

UDP是一個無連接協議,客戶端不會在乎服務端有沒有問題,客戶端只管發,通常用於實時性比較高的領域

例如直播行業

服務端

package main
import (
    "fmt"
    "net"
)
func main() {
    listen err := net.ListenUDP("udp" &net.UDPAddr{
        IP:   net.IPv4(0 0 0 0)
        Port: 8009
})
    if err != nil {
        panic(fmt.Sprintf("udp啓動失敗,err:%v" err))
}
    defer listen.Close()
    for{
        var data = make([]byte1024)
        n addr err := listen.ReadFromUDP(data)
        if err != nil {
            panic(fmt.Sprintf("讀取數據失敗,err:%v" err))
        }
        fmt.Println(string(data[:n])addrn)
}
}

客戶端

package main
import (
    "fmt"
    "net"
)
func main() {
    socker err := net.DialUDP("udp" nil &net.UDPAddr{
        IP:   net.IPv4(0 0 0 0)
        Port: 8009
})
    if err != nil {
        panic(fmt.Sprintf("連接服務器失敗,err:%v" err))
}
    defer socker.Close()
    sendStr:="你好呀"
    _ err = socker.Write([]byte(sendStr))
    if err != nil {
        panic(fmt.Sprintf("數據發送失敗,err:%v" err))
}
}

執行結果

總結


本次章節我們講述了什麼是 TCP,什麼是 UDP。

並且編寫了代碼如何實現 TCP 服務端TCP 客戶端UDP 服務端UDP 客戶端

講述了爲什麼會出現粘包,該怎麼解決粘包

逆水行舟,不進則退!

如果在操作過程中有任何問題,記得下面留言,我們看到會第一時間解決問題。

我是碼農星期八,如果覺得還不錯,記得動手點贊一下哈。

感謝你的觀看。

如果你覺得文章還可以,記得點贊留言支持我們哈。感謝你的閱讀,有問題請記得在下方留言噢~

想學習更多關於 Go 的知識,可以參考學習網址:http://pdcfighting.com/,點擊閱讀原文,可以直達噢~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/FBTJ6Dqy-9xzjfbmHID7xA