脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 一篇文章带你了解Go语言基础之网络编程

一篇文章带你了解Go语言基础之网络编程

2020-12-30 22:58Go语言进阶学习Go进阶者 Golang

本次章节我们讲述了什么是TCP,什么是UDP。并且编写了代码如何实现TCP服务端,TCP客户端,UDP服务端,UDP客户端。讲述了为什么会出现粘包,该怎么解决粘包。

一篇文章带你了解Go语言基础之网络编程

前言

Hi,大家好呀,我是码农,星期八,我们身处21世纪,我们的世界已经在不知不觉中,就像很多网一样在互联互通。

互联网是一个统称,目前比较常用的有TCP,UDP协议。

当然,还有很多其他的协议,但是本次主要讲最常用的TCP和UDP协议。

socker编程

我们所学的TCP和UDP,统称为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("发送成功"

        } 

执行结果

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

一篇文章带你了解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

执行结果

一篇文章带你了解Go语言基础之网络编程

如果我注释了第18行代码

一篇文章带你了解Go语言基础之网络编程

执行结果

一篇文章带你了解Go语言基础之网络编程

直接都淦到一行了,what?这是啥情况,不应该跟原来一样吗???

每发送一个值,那边就接收一下,这怎么整到一块了!!!

原因

主要原因是因为我们是应用层软件,是跑在操作系统之上的软件,当我们向服务器发送一个数据时,是调用操作系统的相关接口发送的,操作系统再经过各种复杂的操作,发送到对方机器

但是操作系统有一个发送数据缓冲区,默认情况如果缓冲区是有大小的,如果缓冲区没满,是不会发送数据的,所以上述客户端在发送数据时,系统的缓冲区都没满,一直压在了操作系统的缓冲区中,最后发现没数据了,才一次都发送到服务端

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

一篇文章带你了解Go语言基础之网络编程

解决粘包

工具函数

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

socker_sitck/stick.go 

 一篇文章带你了解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

执行结果

一篇文章带你了解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)) 

执行结果

一篇文章带你了解Go语言基础之网络编程

总结

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

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

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

逆水行舟,不进则退!

原文地址:https://mp.weixin.qq.com/s/vBk1wF9uDZKIjhxfFRAgtw

延伸 · 阅读

精彩推荐
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02