首页 > 解决方案 > 如何在 tcp 客户端中停止 io.Copy(file, conn)?

问题描述

我是 go lang 的新手,我在 go 中编写 tcp 代码时遇到了麻烦。

这些是我的代码

// client
package main

import (
    "log"
    "net"
    "os"
    "io"
    "fmt"
)

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:4000")
    if err != nil {
        log.Fatal(err)
    }

    GetFileFromServer(conn, "test.txt")
}

func GetFileFromServer(conn net.Conn, fileName string) {
    file, err := os.Create(fileName)
    if err != nil {
        log.Fatal(err)
        return
    }
    defer file.Close()

    conn.Write([]byte("get:" + fileName))
 
    _, err = io.Copy(file, conn) // **IT DOES NOT STOP!!**
    if err != nil {
        log.Fatal(err)
        return
    }

    fmt.Println("GET DONE")
}
// server
package main

import (
    "bytes"
    "log"
    "net"
    "io"
    "fmt"
    "strings"
    "os"
)

const BUFFER_SIZE = 1024
const DIR_PATH = "./serverFile"

func main() {
    listen, err := net.Listen("tcp", "127.0.0.1:4000")
    if err != nil {
        log.Fatal(err)
    }

    for {
        conn, err := listen.Accept()
        if err != nil {
            log.Fatal(err)
        }
        defer conn.Close()

        go ConnHandle(conn)        
    }
}

func ConnHandle(conn net.Conn) {
    buffer := make([]byte, BUFFER_SIZE)
    _, err := conn.Read(buffer)
    if err != nil {
        log.Fatal(err)
    }
    
    trimedBuffer := bytes.Trim(buffer, "\x00")
    command := strings.Split(string(trimedBuffer), ":")

    if command[0] == "get" {
        PutFileToClient(conn, command[1])
        return
    }
}

func PutFileToClient(conn net.Conn, fileName string) {

    file, err := os.Open(DIR_PATH + "\\" + fileName)
    if err != nil {
        log.Fatal(err)
        return
    }
    defer file.Close()

    _, err = io.Copy(conn, file)
    if err != nil {
        log.Fatal(err)
        return
    }
    
    fmt.Println("PUT DONE")
}

- - 我想要的是 - -

  1. 服务器和客户端被执行,
  2. 客户端发送到服务器“get:test.txt”
  3. 服务器收到
  4. 服务器向客户端发送文件
  5. 客户收到它,并保存在文件中
  6. 终止客户端

我认为“客户端的 io.Copy”没有停止的原因是因为它一直试图从连接中读取数据。

如何修复或停止它?

标签: gotcp

解决方案


正如@BurakSerdar 提到的,客户端永远不会得到 aio.EOF因为服务器永远不会关闭连接流。

func PutFileToClient(conn net.Conn, fileName string) {
    defer conn.Close() // ensures client gets a `io.EOF` on success *OR* failure

    // ...
}

但是,您需要注意一些边缘情况。

虽然可以在服务器端跟踪错误处理,但从客户端的角度来看,它将忽略这些错误并保存以下文件:

  • 零字节(如果服务器无法打开文件);或者
  • 部分字节(如果io.Copy中间有服务器失败)

为了解决这些情况,建议在连接有效负载的开头写一个文件大小的标头,例如

服务器

  • 文件打开:
    • 失败:defer关闭 conn(客户端将获得零字节)
  • 统计文件大小
    • 将文件大小写为 8 字节标头(64 位文件大小涵盖任何大小),例如
      • b := make([]byte, 8)
      • binary.LittleEndian.PutUint64(b, fsize)
      • n, err := io.Copy(conn, b)
    • io. 像以前一样复制文件主体
      • 返回任何错误

客户

  • 将标头读入 8 字节缓冲区
  • 短读(即小于 8 字节)无/错误标头 - 服务器无法获取文件
  • 解组文件大小标头:
    • fsize := binary.LittleEndian.Uint64(b)
  • 复制的轨道字节:
    • n, err := io.Copy(file, conn)
  • 验证n == fsize以确保收到完整的有效载荷。

推荐阅读