【问题标题】:How to halt io.Copy(file, conn) in tcp client?如何在 tcp 客户端中停止 io.Copy(file, conn)?
【发布时间】:2020-10-31 16:50:24
【问题描述】:

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

如何修复或停止它?

【问题讨论】:

  • 发送文件时关闭连接。不要使用 defer 来关闭连接,这只会在函数返回时起作用。关闭处理文件副本的 goroutine 中的连接。你可以在那里推迟关闭它。
  • 您还需要解决让客户端知道是否存在服务器端错误的问题。文件打开失败;否则客户端不会注意到短读。
  • 添加了关于如何添加文件大小标头以覆盖一些潜在的服务器端错误的建议。

标签: go tcp


【解决方案1】:

正如@BurakSerdar 提到的,客户端永远不会得到io.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 以确保收到完整的有效负载。

【讨论】:

    猜你喜欢
    • 2021-06-27
    • 1970-01-01
    • 2021-04-04
    • 2019-07-21
    • 2019-12-15
    • 2013-12-06
    • 1970-01-01
    • 1970-01-01
    • 2011-08-15
    相关资源
    最近更新 更多