前言

通过golang实现Tcp的连接与信息传输

本文主要介绍Tcp协议以及如何使用golang来建立一个简单的tcp连接服务,并且实现信息的传输。

首先介绍什么是Tcp协议

Tcp协议是传输层的一个可靠数据传输协议,Tcp协议有以下几个特点:

  • 点对点的发送:一个发送方,一个接收方
  • 可靠性: 可靠的、按序的字节流
  • 流水线机制:TCP拥塞控制和流量控制机制设置滑动窗口尺寸
  • 缓存窗口: 发送方/接收方可以进行缓存
  • 全双工:同一连接中能够传输双向数据流
  • 面向连接:通信双方在发送数据之前必须建立连接,在建立连接之后才能进行数据传输
    • 连接状态只在连接的两端中维护,在沿途节点中并不维护状态(端到端)
    • TCP连接包括:两台主机上的缓存、连接状态变量、socket等(双方都要维护)

什么是可靠数据传输?

TCP在IP层提供的不可靠服务基础上实现的可靠数据传输服务,基于流水线机制。当有发送端的数据丢失后,接收端不会不予理睬,而是重新会发送给发送方一个信号,请求重新发送该数据报。以此来确保数据的可靠性传输。这里只作简单解释可靠数据传输的特点:

  • 累计确认机制:当接收方接收到因为超时重传的帧后,会传输当前累加后的(最大的)ACK序号。
  • TCP使用单一重传定时器(也就是SR定时器,只判断ACK的那个帧进行定时处理)
  • 触发重传的事件:超时、收到重复ACK
  • 渐进式:暂不考虑重复ACK、暂不考虑流量控制、暂不考虑拥塞控制

TCP的快速重传机制

如果TCP通道建立之后,数据在发送过程中丢失。TCP将会触发快速重传机制,下面是快速重传机制的特点:

  • 如果发生超时情况,而超时时间间隔过长,则需要等待很长时间。
  • 当发送方接收到3个重复的ACK,就触发快速重传机制,直接重新发送这个帧数据。

简单介绍TCP连接的三次握手和四次挥手

三次握手

  • 客户端希望与服务端建立TCP连接时,需要先发送一个SYN请求报文段给服务端,并告诉服务端自己的初始报文段序列号是多少。
  • 服务端接收到这个报文后进行随机选择初始的报文段序列号,分配滑动窗口缓存空间大小。接着返回一个SYNACK响应报文段并且把服务端初始报文段序列号和滑动窗口缓存空间大小给客户端表明我已经接到你的请求了。
  • 客户端接收到SYNACK报文段后会答复一个ACK报文段表明我已经收到,可以建立连接了。同时会根据接收到的服务端的滑动窗口缓存空间大小,分配一个同样大小的滑动窗口缓存空间用于发送。

四次挥手

  • 客户端进程发出连接释放的报文FIN=1以及一个客户端的序列号(该序列号等于最后一个传进来的数据的序列号+1)给服务端,并进入FIN_WAIT_1的终止等待状态。TCP规定FIN报文段即使不携带数据,也要消耗一个序号。
  • 服务端收到客户端发来的请求报文和序列号后,响应给客户端ACK=1确认报文段,服务端的报文序列号,以及ack=u+1。此时服务端进入close_wait状态(关闭等待状态)。此时TCP通知上层应用进程,客户端已经准备关闭了,这时候处于版关闭状态。这时如果向客户端发送数据,客户端仍然需要接收。这个状态需要维持一段时间,如果期间有数据需要发送就进行发送。等待整个CLOSE_WAIT状态持续时间结束。
  • 客户端收到服务端发来的ACK=1确认报文后,进入FIN_WAIT_2的终止等待状态,等待服务端是否还有数据需要进行发送。
  • 服务端发送完最后的数据之后,就向客户端发送连接释放报文,FIN=1,ack=u+1。由于在半关闭状态,服务器可能还会发送一些数据,所以这时的序列号也会随之改变。服务端发送完的报文序列号之后就进入LAST_ACK最后确认状态,等待客户端进行确认。
  • 客户端收到服务端发送的连接释放报文后必须发送确认ACK=1报文,以及自己的序列号给服务端表示已经接收并进入TIME-WAIT(时间等待状态)注意此时客户端并未关闭,而是经过2*MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB之后才进入CLOSED状态。
  • 服务端只要接收到客户端的确认连接释放报文,就立即进入CLOSED关闭状态,同样撤销掉了TCB之后就结束了这次的TCP连接。因此可以看出,(除非特殊情况)服务端关闭是要早于客户端的。

golang实现简单的tcp连接建立

服务端

主要分为3部分

  • 建立tcp监听通道,指定监听端口
net.Listen("tcp", "127.0.0.1:4399")  (Listener, error)
  • 对通道进行监听
listen.Accept() (Conn, error)
  • 关闭监听通道
defer listen.Close()

完整代码

注意defer语句一定要写在错误处理之后。如果写在错误之前,一旦发生了错误,该连接就不会被生成,进而执行defer语句的时候无法进行通道关闭。

package main
import (
   "fmt"
   "net"
)
func handle(conn net.Conn) {
   defer conn.Close()
   var info [256]byte
   n, err := conn.Read(info[:])
   if err != nil {
      fmt.Println("conn Read fail ,err = ", err)
      return
   }
   fmt.Println("client send info to server si : ", string(info[:n]))
}
func main() {
   // 1. 建立tcp连接监听通道
   listen, err := net.Listen("tcp", "127.0.0.1:4399")
   if err != nil {
      panic(err)
   }
   // 3. 关闭监听通道
   defer listen.Close()
   fmt.Println("server is Listening")
   for {
      // 2. 进行通道监听
      conn, err := listen.Accept()
      if err != nil {
         panic(err)
      }
      // 启动一个协程去单独处理该连接
      go handle(conn)
   }
}

客户端

客户端和服务端一样,也分为三个部分

  • 对指定通道进行连接
net.Dial("tcp", "127.0.0.1:4399") (Conn, error)
  • 连接成功后发送数据
msg := "Hi, I am a client"
conn.Write([]byte(msg))
  • 发送完成后进行关闭连接
defer conn.Close()

完整代码

在这里我只做了简单的处理,将字符串转化为字符切片通过Write的方式发送给了服务端,并且该过程只进行了一次。如果需要多次持续建立连接并且发送,需要主动开启一个for循环,并且设置循环结束条件。

package main
import "net"
func main() {
   // 1. 建立访问通道
   conn, err := net.Dial("tcp", "127.0.0.1:4399")
   if err != nil {
      panic(err)
   }
   defer conn.Close()
   msg := "Hi, I am a client"
   conn.Write([]byte(msg))
}

以上就是golang实现简单的tcp数据传输的详细内容,更多关于golang tcp数据传输的资料请关注其它相关文章!

原文地址:https://juejin.cn/post/7175507626199875639

相关文章: