如何在 tcp 客户端中停止 io.Copy(file, conn)?

How to halt io.Copy(file, conn) in tcp client?

我是 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”没有停止的原因是它一直在尝试从连接中读取数据。

如何修复或停止它?

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

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

    // ...
}

但是,您需要注意一些极端情况。

虽然可以在 server-side 上跟踪错误处理,但从客户端的角度来看,它会忽略这些错误并保存一个文件:

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

为了解决这些情况,建议在连接负载的开头写一个 file-size header,例如

服务器

  • 文件打开:
    • 失败:defer 关闭连接(客户端将获得零字节)
  • 统计数据file-size
    • 将文件大小写入 8 字节 header(64 位文件大小涵盖任何大小)例如
      • b := make([]byte, 8)
      • binary.LittleEndian.PutUint64(b, fsize)
      • n, err := io.Copy(conn, b)
    • io.Copy 文件 body 和以前一样
      • return 任何错误

客户

  • 将 header 读入 8 字节缓冲区
  • short-read(即少于 8 个字节)no/bad header - 服务器无法获取文件
  • 解组 file-size header:
    • fsize := binary.LittleEndian.Uint64(b)
  • 轨道字节复制:
    • n, err := io.Copy(file, conn)
  • 验证 n == fsize 以确保收到完整的负载。