Go语言TCP文件传输:服务端读取无限循环问题求助
解决TCP文件传输中服务端无限循环无法识别EOF的问题
你的问题核心在于TCP是无边界的字节流协议,服务端无法天然区分"文件内容结束"和"客户端暂时没发数据",所以io.Copy会一直阻塞等待,直到连接关闭或出错。下面是两种无需超时、停止词的Go语言解决方案:
方案1:传输文件大小作为边界标记
在发送文件名后,先传输文件的字节长度,服务端读取到长度后,只读取对应字节数的内容,就能准确判断文件结束位置,无需依赖连接关闭。
客户端代码示例
package main import ( "bufio" "fmt" "io" "log" "net" "os" "strings" ) func main() { // 打开要发送的文件 filePath := "test.txt" f, err := os.Open(filePath) if err != nil { log.Fatalf("open file failed: %v", err) } defer f.Close() // 获取文件信息(文件名、大小) fileInfo, err := f.Stat() if err != nil { log.Fatalf("get file stat failed: %v", err) } fileName := fileInfo.Name() fileSize := fileInfo.Size() // 连接服务端 conn, err := net.Dial("tcp", "localhost:8080") if err != nil { log.Fatalf("dial server failed: %v", err) } defer conn.Close() // 发送文件名(用换行分隔) if _, err := fmt.Fprintf(conn, "%s\n", fileName); err != nil { log.Fatalf("send filename failed: %v", err) } // 发送文件大小(用字符串+换行简化,生产环境建议用binary包保证跨平台字节序) if _, err := fmt.Fprintf(conn, "%d\n", fileSize); err != nil { log.Fatalf("send file size failed: %v", err) } // 发送文件内容,只传输指定大小的字节 if _, err := io.CopyN(conn, f, fileSize); err != nil && err != io.EOF { log.Fatalf("send file content failed: %v", err) } // 读取服务端响应 resp, err := bufio.NewReader(conn).ReadString('\n') if err != nil { log.Printf("read response failed: %v", err) } log.Printf("server response: %s", strings.TrimSpace(resp)) }
服务端代码示例
package main import ( "bufio" "fmt" "io" "log" "net" "os" "strconv" "strings" ) func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { log.Fatalf("listen failed: %v", err) } defer listener.Close() log.Println("server listening on :8080") for { conn, err := listener.Accept() if err != nil { log.Printf("accept failed: %v", err) continue } go handleConnection(conn) } } func handleConnection(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) // 读取文件名 fileName, err := reader.ReadString('\n') if err != nil { log.Printf("read filename failed: %v", err) fmt.Fprintf(conn, "ERR: read filename failed\n") return } fileName = strings.TrimSpace(fileName) // 读取文件大小 sizeStr, err := reader.ReadString('\n') if err != nil { log.Printf("read file size failed: %v", err) fmt.Fprintf(conn, "ERR: read file size failed\n") return } fileSize, err := strconv.ParseInt(strings.TrimSpace(sizeStr), 10, 64) if err != nil { log.Printf("parse file size failed: %v", err) fmt.Fprintf(conn, "ERR: invalid file size\n") return } // 创建输出文件 outFile, err := os.Create(fileName) if err != nil { errMsg := fmt.Sprintf("create file failed: %v", err) log.Println(errMsg) fmt.Fprintf(conn, "ERR: %s\n", errMsg) return } defer outFile.Close() // 读取指定大小的内容写入文件 _, err = io.CopyN(outFile, reader, fileSize) if err != nil && err != io.EOF { errMsg := fmt.Sprintf("write file failed: %v", err) log.Println(errMsg) fmt.Fprintf(conn, "ERR: %s\n", errMsg) return } // 返回成功响应 fmt.Fprintf(conn, "OK: file saved successfully\n") log.Printf("file %s saved, size: %d bytes", fileName, fileSize) }
优缺点:适合多文件连续传输场景,无需断开连接;但需要提前知道文件大小,动态生成的流式内容不适用。
方案2:客户端发送完文件后关闭写端
TCP是全双工协议,客户端可以单独关闭连接的写端(CloseWrite()),此时服务端会收到EOF信号,io.Copy会自动停止读取,同时客户端仍能读取服务端的响应。
客户端代码调整(核心部分)
// 发送文件内容 if _, err := io.Copy(conn, f); err != nil { log.Fatalf("send file content failed: %v", err) } // 关闭写端,告知服务端数据已发送完毕 if err := conn.CloseWrite(); err != nil { log.Printf("close write failed: %v", err) } // 读取服务端响应 resp, err := bufio.NewReader(conn).ReadString('\n') if err != nil { log.Printf("read response failed: %v", err) } log.Printf("server response: %s", strings.TrimSpace(resp))
服务端代码简化(核心部分)
func handleConnection(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) // 读取文件名 fileName, err := reader.ReadString('\n') if err != nil { log.Printf("read filename failed: %v", err) fmt.Fprintf(conn, "ERR: read filename failed\n") return } fileName = strings.TrimSpace(fileName) // 创建文件 outFile, err := os.Create(fileName) if err != nil { errMsg := fmt.Sprintf("create file failed: %v", err) log.Println(errMsg) fmt.Fprintf(conn, "ERR: %s\n", errMsg) return } defer outFile.Close() // io.Copy会在客户端关闭写端时收到EOF并停止 _, err = io.Copy(outFile, reader) if err != nil && err != io.EOF { errMsg := fmt.Sprintf("write file failed: %v", err) log.Println(errMsg) fmt.Fprintf(conn, "ERR: %s\n", errMsg) return } // 返回成功响应 fmt.Fprintf(conn, "OK: file saved\n") log.Printf("file %s saved", fileName) }
优缺点:实现简单,无需额外传输元数据;但单次连接只能传输一个文件,若要传多个需重新协商边界。
内容的提问来源于stack exchange,提问作者GhostVision
相关产品推荐
相关产品推荐

