一文搞懂Go语言网络编程【tcp、udp】

简介: 一文搞懂Go语言网络编程【tcp、udp】

文章目录



前言


本文介绍的不是http.net包,仅仅介绍传统的网络通信(后期会单独进行http.net包的更新)


一、互联网的层次结构


大致分为四层,细分的话可以分为7层


1.应用层


应用层、表示层、会话层


2.传输层


传输层


3.网络层


网络层


4.网络接口层


数据链路层、 物理层


5.图解


f48327d49c304730b23b2675600bc0c6.png


  其中socket层是为我们准备的,我们可以在这一层进行一些功能的实现达到用户的需求


二、tcp协议概述


1.三次握手


第一次握手


客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,
以及初始序号X,保存在包头的序列号(Sequence Number)字段里。


第二次握手


服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同
时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。


第三次握手


客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服
务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1


2.tcp通常用来做什么?


  • 由于tcp通信数据包传输精确可以用来传输文件图片,等重要的数据


  • tcp传输准确率高但效率低,不易传数据量大重要程度不高的数据


3.代码实现tcp通信


客户端代码如下:


package main
import (
  "bufio"
  "fmt"
  "net"
  "os"
  "strings"
  "time"
)
// tcp/client/main.go
// 接收数据
func getMsg(conn net.Conn) {
  for {
    buf := [512]byte{}
    // n为获取到的数据长度
    n, err := conn.Read(buf[:])
    if err != nil {
      fmt.Println("recv failed, err:", err)
      return
    }
    fmt.Println(string(buf[:n]))
  }
}
// 发送数据
func sendMsg(conn net.Conn) {
  for {
    // 创建一个缓冲区阅读器
    inputReader := bufio.NewReader(os.Stdin)
    // 接收用户输入,以\n为终结符
    input, _ := inputReader.ReadString('\n') // 读取用户输入
    // 去掉获取到的\n
    inputInfo := strings.Trim(input, "\r\n")
    // 判断是否满足退出条件
    if strings.ToUpper(inputInfo) == "Q" {
      // 对服务器发送退出请求
      _, _ = conn.Write([]byte("q"))
      return
    }
    // 给服务端写入数据(数据在两端之间以字节的形式传输)
    _, err := conn.Write([]byte(inputInfo))
    if err != nil {
      return
    }
  }
}
// 客户端
func main() {
  // 使用tcp的方式去连接127.0.0.1:20000
  conn, err := net.Dial("tcp", "127.0.0.1:20000")
  if err != nil {
    fmt.Println("err :", err)
    return
  }
  // 在函数执行到末端的时候进行关闭连接
  defer conn.Close()
  go getMsg(conn)
  go sendMsg(conn)
  time.Sleep(time.Hour)
}


服务端代码如下:


package main
import (
  "bufio"
  "fmt"
  "net"
  "os"
  "strings"
)
// TCP server端
func sendMsg(conn net.Conn) {
  for {
    inputer := bufio.NewReader(os.Stdin)
    input, _ := inputer.ReadString('\n')
    input = strings.Trim(input, "\r\n")
    conn.Write([]byte(input))
  }
}
// 接收处理函数
func process(conn net.Conn) {
  fmt.Println("线路:", conn, "连接成功!")
  defer conn.Close() // 关闭连接
  for {
    reader := bufio.NewReader(conn)
    var buf [128]byte
    n, err := reader.Read(buf[:]) // 读取数据
    if err != nil {
      fmt.Println("read from client failed, err:", err)
      break
    }
    recvStr := string(buf[:n])
    if recvStr == "q" {
      fmt.Println(conn, "断开了连接!")
      return
    }
    fmt.Println(conn, ":", recvStr)
    fmt.Printf("请输入:")
  }
}
func main() {
  // 绑定网关的127.0.0.1:20000,以tcp的形式进行数据传输
  listen, err := net.Listen("tcp", "127.0.0.1:20000")
  if err != nil {
    fmt.Println("listen failed, err:", err)
    return
  }
  for {
    // 等待客户端进行连接
    conn, err := listen.Accept() // 建立连接
    if err != nil {
      fmt.Println("accept failed, err:", err)
      continue
    }
    // 将接受到的连接添加到后台进程
    go process(conn)
    go sendMsg(conn)
  }
}


三、udp协议概述


1.udp通常用来做什么?


  • udp传输效率高,但容易丢失数据
  • 可用来进行网络直播,视频通话
  • 保留重要的信息,对可有可无的信息尽力保存


2.代码实现


客户端代码如下:


package main
import (
  "bufio"
  "fmt"
  "net"
  "os"
)
// UDP client
func main() {
  socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
    IP:   net.IPv4(127, 0, 0, 1),
    Port: 40000,
  })
  if err != nil {
    fmt.Println("连接服务端失败,err:", err)
    return
  }
  defer socket.Close()
  var reply [1024]byte
  reader := bufio.NewReader(os.Stdin)
  for {
    fmt.Print("请输入内容:")
    msg, _ := reader.ReadString('\n')
    socket.Write([]byte(msg))
    // 收回复的数据
    n, _, err := socket.ReadFromUDP(reply[:])
    if err != nil {
      fmt.Println("redv reply msg failed,err:", err)
      return
    }
    fmt.Println("收到回复信息:", string(reply[:n]))
  }
}


服务端代码如下:


package main
import (
  "fmt"
  "net"
  "strings"
)
// UDP server
func main() {
  // 建立UDP服务端,不需要使用accept方法
  conn, err := net.ListenUDP("udp", &net.UDPAddr{
    IP:   net.IPv4(127, 0, 0, 1),
    Port: 40000,
  })
  if err != nil {
    fmt.Println("listen UDP failed,err:", err)
    return
  }
  defer conn.Close()
  // 不需要建立连接,直接收发数据
  var data [1024]byte
  for {
    n, addr, err := conn.ReadFromUDP(data[:])
    if err != nil {
      fmt.Println("read from UDP failed,err:", err)
      return
    }
    fmt.Println(data[:n])
    reply := strings.ToUpper(string(data[:n]))
    // 发送数据
    conn.WriteToUDP([]byte(reply), addr)
  }
}


四、粘包问题【及解决方法】


1.为什么会粘包?


由于频繁的进行数据的收发,底层为了提高收发效率,在发送消息前会检查一下是否还有需要发送的其他数据


如果发送过于频繁,上一次数据包将与夏下一次的数据报粘在一起,导致数据非常乱


由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去


/*
  在服务端接收客户端发送来的20次Tomhello并打印
  在粘包下的打印效果(各个信息有可能会直接相连)
  hello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello
   Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tom
  --------------
  hello Tomhello Tomhello Tom
  --------------
  正常的打印结果为:
  --------------
  hello Tomhello 
  --------------
  hello Tomhello 
  --------------
  hello Tomhello 
  --------------
  hello Tomhello 
  --------------
  hello Tomhello 
  --------------
  hello Tomhello 
  --------------
  hello Tomhello 
  --------------
  hello Tomhello 
  --------------
  ......
*/



2.解决方案


  • 将每一个数据包大小进行保存,数据包写一表头用于存储数据包的大小,每次进行数据的读取时先
  • 进行数据包大小的读取,在进行数据信息实体的读取
  • 降低发送数据的频率


3.编码解码函数


编码解码函数代码如下:


package edcode
import (
  "bufio"
  "bytes"
  "encoding/binary"
)
/*
  小端低低:低地址存低位
  大端高低:高地址存低位
  对需要发送的信息进行编码解码(打包处理)
  其中包内的前四个字节用于存储包的大小,后面用于存储信息的主要内容
*/
// 解码
// 取出bytes前四个字节,转化为int32类型,然后再将剩余的消息体取出
func Mydecode(reader *bufio.Reader) (string, error) {
  Size, _ := reader.Peek(4)
  lenthbuff := bytes.NewBuffer(Size)
  var lenth int32
  // 读取lenthbuff内的内容,读取完将数据放在lenth内,读取的方式是小端方式
  err := binary.Read(lenthbuff, binary.LittleEndian, &lenth)
  // 判断一下是否出错,长度不够包头指定的长度就抛异常
  if err != nil || int32(reader.Buffered()) < lenth+4 {
    return "", err
  }
  //
  pack := make([]byte, int(lenth+4))
  // read读取之后缓冲区就少一部分数据
  _, err = reader.Read(pack)
  if err != nil {
    return "", err
  }
  return string(pack[4:]), nil
}
// 编码
// 先计算出字符串的长度,再进行打包
func MyEncode(message string) ([]byte, error) {
  // 读取消息的长度,转换成int32类型(占4个字节)
  var length = int32(len(message))
  var pkg = new(bytes.Buffer)
  // 写入消息头
  err := binary.Write(pkg, binary.LittleEndian, length)
  if err != nil {
    return nil, err
  }
  // 写入消息实体
  err = binary.Write(pkg, binary.LittleEndian, []byte(message))
  if err != nil {
    return nil, err
  }
  return pkg.Bytes(), nil
}


客户端代码如下:


package main
import (
  "fmt"
  "net"
  ed "aCorePackage/edcode"
)
func main() {
  conn, err := net.Dial("tcp", "127.0.0.1:20000")
  if err != nil {
    fmt.Println(err)
  }
  // 快速发送信息还是会黏在一起,只不过通过一定的编码解码方式可以使信息很好的分离
  for i := 0; i < 20; i++ {
    str := "hello Tom"
    mybtys, err := ed.MyEncode(str)
    if err != nil {
      fmt.Println(err)
    }
    conn.Write(mybtys)
    fmt.Println("-------------------")
  }
}


服务端代码如下:


package main
import (
  ed "aCorePackage/edcode"
  "bufio"
  "fmt"
  "io"
  "net"
)
func readMsd(conn net.Conn) {
  defer conn.Close()
  reader := bufio.NewReader(conn)
  for {
    str, err := ed.Mydecode(reader)
    if err == io.EOF {
      return
    }
    if err != nil {
      fmt.Println(err)
      return
    }
    fmt.Println(str)
    fmt.Println("--------------")
  }
}
func main() {
  listener, err := net.Listen("tcp", "127.0.0.1:20000")
  if err != nil {
    fmt.Println(err)
  }
  for {
    conn, err := listener.Accept()
    if err != nil {
      fmt.Println(err)
      return
    }
    go readMsd(conn)
  }
}


总结


TCP 需要连接,UDP 是无连接的,发送数据之前不需要建立连接, TCP 提供可靠的服务,通过 TCP 连接传送的数据,无差错,不丢失 TCP 逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道,两者各有所长,对他们进行各取所需。

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
5月前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
336 61
|
4月前
|
监控 安全 Go
使用Go语言构建网络IP层安全防护
在Go语言中构建网络IP层安全防护是一项需求明确的任务,考虑到高性能、并发和跨平台的优势,Go是构建此类安全系统的合适选择。通过紧密遵循上述步骤并结合最佳实践,可以构建一个强大的网络防护系统,以保障数字环境的安全完整。
132 12
|
5月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
5月前
|
运维 网络协议 Go
Go网络编程:基于TCP的网络服务端与客户端
本文介绍了使用 Go 语言的 `net` 包开发 TCP 网络服务的基础与进阶内容。首先简述了 TCP 协议的基本概念和通信流程,接着详细讲解了服务端与客户端的开发步骤,并提供了简单回显服务的示例代码。同时,文章探讨了服务端并发处理连接的方法,以及粘包/拆包、异常检测、超时控制等进阶技巧。最后通过群聊服务端的实战案例巩固知识点,并总结了 TCP 在高可靠性场景中的优势及 Go 并发模型带来的便利性。
|
9月前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
318 20
|
9月前
|
网络协议 算法 安全
Go语言的网络编程与TCP_UDP
Go语言由Google开发,旨在简单、高效和可扩展。本文深入探讨Go语言的网络编程,涵盖TCP/UDP的基本概念、核心算法(如滑动窗口、流量控制等)、最佳实践及应用场景。通过代码示例展示了TCP和UDP的实现,并讨论了其在HTTP、DNS等协议中的应用。最后,总结了Go语言网络编程的未来发展趋势与挑战,推荐了相关工具和资源。
297 5
|
11月前
|
负载均衡 网络协议 算法
不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?
这网络层就像搭积木一样,上层协议都是基于下层协议搭出来的。不管是ping(用了ICMP协议)还是tcp本质上都是基于网络层IP协议的数据包,而到了物理层,都是二进制01串,都走网卡发出去了。 如果网络环境没发生变化,目的地又一样,那按道理说他们走的网络路径应该是一样的,什么情况下会不同呢? 我们就从路由这个话题聊起吧。
292 4
不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?
|
9月前
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
286 0
|
数据库连接 Go 数据库
Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性
本文探讨了Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性;防御编程则强调在编码时考虑各种错误情况,确保程序健壮性。文章详细介绍了这两种技术在Go语言中的实现方法及其重要性,旨在提升软件质量和可靠性。
194 1
|
11月前
|
Go 数据安全/隐私保护 UED
优化Go语言中的网络连接:设置代理超时参数
优化Go语言中的网络连接:设置代理超时参数