Go语言实现TCP通信

TCP协议为传输控制协议,TCP协议有以下几个特点:

1. TCP是面向连接的传输层协议;

2. 每条TCP连接只能有两个端点,每条TCP连接是点到点的通信;

3. TCP提供可靠的交付服务,保证传送的数据无差错,不丢失,不重要且有序;

4. TCP提供全双工通信,允许双方在任何时候都能发送数据,为此TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据;

5. TCP是面向字节流的;

发送缓存用来暂存以下数据:

① 发送应用程序传送给发送方TCP准备发送的数据;

② TCP已发送但尚未收到确认的数据;

接收缓存用来暂存以下数据:

① 按序到达但尚未被接收应用程序读取的数据;

② 不按序到达的数据;

因为是面向连接的协议,数据像水流一样传输,会存在黏包问题。

TCP连接

一个TCP服务端可以同时连接很多个客户端,Go语言可以使用go关键字开启goroutine,每建立一个连接就创建一个goroutine,这样可以并发执行每一个创建的连接,tcp服务端主要的处理流程有:1. 监听端口;2. 接收客户端请求创建tcp连接;3. 使用go关键字开启goroutine处理每一个建立的连接,收发数据;4. 关闭连接;tcp客户端主要的处理流程有:1. 建立与服务端的连接;2. 收发数据;3. 关闭连接;关于tcp通信我们一般会使用到bufio,net,strings,os包,其中bufio包主要用来做输入输出数据的缓存,bufio.NewReader()函数可以传递os.Stdin类型(任何实现了io.Reader接口中的read()方法都可以作为参数传递进来,通常是传递一个实现了io.Reader接口中的read()方法的结构体或者是结构体指针),os.Stdin为标准输入,我们可以接受读取从控制台输入的数据,os.NewReader()函数返回的是一个新的带有4096 byte大小缓冲区的Reader结构体指针类型,通过Reader结构体指针类型我们就可以调用很多的方法,比如可以调用ReadString(delim byte) (string,error)方法,ReadString方法可以一直从标准输入中读取数据,直到遇到指定的终止符号delim,并且读取的内容会包含当前的delim,所以bufio包主要用来对输入和输出的数据进行缓冲;net包主要是连接的监听、创建,以及连接数据的读取和写入(例如tcp,udp等网络编程)等相关工作,tcp服务端中可以使用一个process()函数传递一个net.Conn类型也即*net.TCPConn类型的变量conn(TCPConn结构体实现了Conn接口),*TCPConn类型那么就可以调用很多的方法,因为我们需要从tcp连接中读取客户端或者是服务端发送的数据,所以可以调用*TCPConn类型的read()方法读取tcp连接的数据,然后输出数据即可,服务端可以使用net.Listen()函数监听连接,使用Accept()方法建立tcp连接,客户端则可以使用net.Dial()连接创建的tcp连接,使用conn接口的实现*TCPConn(Dial()方法的返回值就是*TCPConn类型)调用conn的read()方法将读取tcp连接的内容,write()方法将数据写入到tcp连接中;strings包主要是对读取到的数据的字符串形式进行处理,比如去除掉字符串的一些符号等等。

下面的代码需要先运行服务端的代码,再运行客户端的代码:

TCP服务端

package main
 
import (
        "bufio"
        "fmt"
        "net"
        "os"
        "strings"
)
 
// TCP 服务端
func process(conn net.Conn) {
        // 函数执行完之后关闭连接
        defer conn.Close()
        // 输出主函数传递的conn可以发现属于*TCPConn类型, *TCPConn类型那么就可以调用*TCPConn相关类型的方法, 其中可以调用read()方法读取tcp连接中的数据
        fmt.Printf("服务端: %T\n", conn)
        for {
                var buf [128]byte
                // 将tcp连接读取到的数据读取到byte数组中, 返回读取到的byte的数目
                n, err := conn.Read(buf[:])
                if err != nil {
                        // 从客户端读取数据的过程中发生错误
                        fmt.Println("read from client failed, err:", err)
                        break
                }
                recvStr := string(buf[:n])
                fmt.Println("服务端收到客户端发来的数据:", recvStr)
                // 由于是tcp连接所以双方都可以发送数据, 下面接收服务端发送的数据这样客户端也可以收到对应的数据
                inputReader := bufio.NewReader(os.Stdin)
                s, _ := inputReader.ReadString('\n')
                t := strings.Trim(s, "\r\n")
                // 向当前建立的tcp连接发送数据, 客户端就可以收到服务端发送的数据
                conn.Write([]byte(t))
        }
}
 
func main() {
        // 监听当前的tcp连接
        listen, err := net.Listen("tcp", "127.0.0.1:20000")
        fmt.Printf("服务端: %T=====\n", listen)
        if err != nil {
                fmt.Println("listen failed, err:", err)
                return
        }
        for {
                conn, err := listen.Accept() // 建立连接
                fmt.Println("当前建立了tcp连接")
                if err != nil {
                        fmt.Println("accept failed, err:", err)
                        continue
                }
                // 对于每一个建立的tcp连接使用go关键字开启一个goroutine处理
                go process(conn) 
        }
}

  

TCP客户端

package main
 
import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)
 
func main() {
    // 连接到服务端建立的tcp连接
    conn, err := net.Dial("tcp", "127.0.0.1:20000")
    // 输出当前建Dial函数的返回值类型, 属于*net.TCPConn类型
    fmt.Printf("客户端: %T\n", conn)
    if err != nil {
        // 连接的时候出现错误
        fmt.Println("err :", err)
        return
    }
    // 当函数返回的时候关闭连接
    defer conn.Close() 
    // 获取一个标准输入的*Reader结构体指针类型的变量
    inputReader := bufio.NewReader(os.Stdin)
    for {
        // 调用*Reader结构体指针类型的读取方法
        input, _ := inputReader.ReadString('\n') // 读取用户输入
        // 去除掉\r \n符号
        inputInfo := strings.Trim(input, "\r\n")
        // 判断输入的是否是Q, 如果是Q则退出
        if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
            return
        }
        _, err = conn.Write([]byte(inputInfo)) // 发送数据
        if err != nil {
            return
        }
        buf := [512]byte{}
        // 读取服务端发送的数据
        n, err := conn.Read(buf[:])
        if err != nil {
            fmt.Println("recv failed, err:", err)
            return
        }
        fmt.Println("客户端接收服务端发送的数据: ", string(buf[:n]))
    }
}