go - 如何在 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")
}
- - 我想要的是 - -
- 服务器和客户端被执行,
- 客户端发送到服务器“get:test.txt”
- 服务器收到
- 服务器向客户端发送文件
- 客户收到它,并保存在文件中
- 终止客户端
我认为“客户端的 io.Copy”没有停止的原因是因为它一直试图从连接中读取数据。
如何修复或停止它?
解决方案
正如@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 字节标头(64 位文件大小涵盖任何大小),例如
客户
- 将标头读入 8 字节缓冲区
- 短读(即小于 8 字节)无/错误标头 - 服务器无法获取文件
- 解组文件大小标头:
fsize := binary.LittleEndian.Uint64(b)
- 复制的轨道字节:
n, err := io.Copy(file, conn)
- 验证
n == fsize
以确保收到完整的有效载荷。
推荐阅读
- swiftui - SwiftUI 上下文菜单是否使用 LayoutConstraints?
- python - 确定 tkinter UI 类属性是否已从外部模块更改的最佳方法是什么?
- javascript - Firebase 数据库打印数据并获取每个元素的值
- java - 如何在 2D 矩阵中找到对角线的总和?
- oracle - CallableStatement PL/SQL 未在 jsp 中插入数据库
- install4j - 在 install4j 中,executeScheduleUpdate 是如何工作的?
- c# - .NET Core 3 中 CreateToken 和 CreateJwtSecurityToken 的区别
- javascript - Bootsrap 模态未显示
- css - 如何在 .RPres R 演示文稿中编辑标题幻灯片样式?
- flutter - 使用 ListTile() 将从 Api 加载的图像排列到 Flutter 中的两列