Maison  >  Article  >  développement back-end  >  Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

Go语言进阶学习
Go语言进阶学习avant
2023-07-21 16:19:191015parcourir
socker编程

我们所学的TCPTCPUDP,统称为SockerUDP,统称为Socker编程,也叫做

套接字编程多台机器要实现互相通讯,其实是一个非常复杂的过程,底层从铺设网线网线接口 ..

Accédez à la couche d'applicationQQQQ微信等软件。

如果没有一套标准,每次使用都要自己去实现,可能每个程序员都不是掉头发那么简单了!

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

有了Socker之后,Socker会在应用层之前,将各种繁琐的的底层操作隐藏,我们可能只需要Socker.TCP就实现了TCP, WeChat

et autres logiciels. 🎜🎜

🎜if Il n'y a pas d'ensemble de normes. Vous devez l'implémenter vous-même à chaque fois que vous l'utilisez. Peut-être que chaque programmeur n'est pas aussi simple que de perdre ses cheveux ! 🎜🎜

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go🎜🎜 Compris🎜🎜 Socker Après 🎜🎜, 🎜🎜Socker.TCP🎜🎜 est atteint🎜🎜TCP🎜🎜 protocole de communication. 🎜🎜🎜

Go langage TCP

TCP est une connexion longue et stable

Étant donné que la communication est impliquée, il doit y avoir deux terminaux, au moins un est le serveur , l'un est le client , tout comme notre Taobao. Chaque fois que nous ouvrons Taobao, nous devons créer un lien vers celui-ci. Bien sûr, Taobao n'est pas directement TCP.

Serveur

Implémentez le serveur dans Go, et la concurrence sur le serveur est très simple, il vous suffit de laisser une coroutine gérer chaque connexion !

Code

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)
}
}

Client

Relativement parlant, il ne nécessite pas de concurrence, il nécessite seulement une connexion.

代码

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("发送成功")
        }
}
}

执行结果

就这样,我们实现了服务端并发的处理所有客户端的请求。

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

粘包

我们先看一下什么是粘包。

服务端

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

执行结果

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

Si je commente la 18ème ligne de code

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

le résultat de l'exécution

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

est directement ajouté à une ligne, quoi ? Que se passe-t-il ? Cela ne devrait-il pas être la même chose qu'avant ? ? ?

Chaque fois qu'une valeur est envoyée, elle est reçue là-bas. Comment cela peut-il être intégré en un seul morceau ? ! !

La raison

La raison principale est que nous sommes une couche d'applicationun logiciel fonctionnant sur le système d'exploitation Lorsque nous envoyons une donnée à. le serveur, Il est envoyé en appelant l' interface associée du système d'exploitation Le système d'exploitation effectue ensuite diverses opérations complexes et l'envoie à l'autre machine

Cependant. , le système d'exploitation dispose d'un tampon d'envoi de données. Par défaut, si le tampon a une certaine taille, les données ne seront pas envoyées si le tampon n'est pas plein. Par conséquent, lorsque le client ci-dessus envoie des données, le tampon système n'est pas plein. et il est toujours enfoncé dans le tampon du système d'exploitation, j'ai finalement découvert qu'il n'y avait pas de données, donc elles ont été envoyées au serveur d'un seul coup

Mais pourquoisleep(1)又管用了呢?这是因为缓冲区不止一个程序在用,1s的时间足够其他程序将缓冲区打满,然后各自发各自的数据,这也是为什么第一次操作没问题,第二次有问题,因为第二次全部都是我们客户端打满的

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

解决粘包

工具函数

我们将解包封包的函数封装一下

socker_sitck/stick.goÇa fonctionne à nouveau ? C'est parce que le tampon est utilisé par plus d'un programme. 1 s est suffisant pour que d'autres programmes remplissent le tampon et envoient ensuite leurs propres données. d'abord Il n'y a pas eu de problème avec la première opération, mais il y a eu un problème la deuxième fois, car la deuxième fois, tout était entièrement chargé sur notre client

Un article pour vous aider à comprendre les bases de la programmation réseau en langage GoUn article pour vous aider à comprendre les bases de la programmation réseau en langage Go

🎜 Résoudre les sacs collants🎜

🎜Fonction outil🎜

🎜Nous encapsulerons la fonction de déballage du paquet🎜🎜🎜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)
}
}

执行结果

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

这次真的不管执行几次,都是这样的结果

对了,只有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([]byte,1024)
        n, addr, err := listen.ReadFromUDP(data)
        if err != nil {
            panic(fmt.Sprintf("读取数据失败,err:%v", err))
        }
        fmt.Println(string(data[:n]),addr,n)
}
}

客户端

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))
}
}

执行结果

Un article pour vous aider à comprendre les bases de la programmation réseau en langage Go

总结

本次章节我们讲述了什么是TCP,什么是UDP。

并且编写了代码如何实现TCP服务端TCP客户端UDP服务端UDP客户端

讲述了为什么会出现粘包,该怎么解决粘包

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer