基于Golang TCP的FLAC音乐文件传输播放问题求助
FLAC文件TCP传输后无法播放的问题排查与修复
你遇到的FLAC文件无法播放的核心原因是:TCP分块读写时未正确处理实际读取/写入的字节数,导致传输后的文件包含冗余无效数据,破坏了FLAC的严格文件结构。
具体问题分析
服务端代码问题
- 忽略Read返回的实际字节数
在read函数中,reader.Read(buf)返回的n(实际读取的字节数)被丢弃,每次都发送整个15字节缓冲区。当文件最后一块不足15字节时,缓冲区中残留的上一次读取数据会被一并发送,导致文件末尾多出无效字节。 - 错误处理逻辑漏洞
当前判断if err != nil && err == io.EOF逻辑错误:若出现非EOF的读取错误,会继续循环发送数据,导致错误扩散。 - 连接提前关闭
read函数中的defer conn.Close()会在第一个歌曲传输完成后直接关闭连接,后续歌曲无法传输。
客户端代码问题
- 缺失错误处理
net.Dial、reader.Read、os.WriteFile的错误被直接忽略,可能隐藏连接、读取或写入失败的问题。 - 追加整个缓冲区而非实际读取内容
每次读取后直接append(glMessage, b...),但实际仅读取了i字节,缓冲区中未被覆盖的旧数据会被追加到结果中,导致文件体积异常且包含无效数据。
修正后的代码
服务端 downloader_service.go
func DownloadSong(conn net.Conn) { defer conn.Close() // 统一在函数末尾关闭连接,确保所有歌曲传输完成 token, err := bufio.NewReader(conn).ReadString('\n') if err != nil { logger.ErrorLog(fmt.Sprintf("Error: couldn't get token. Details: %v", err)) return } claims, err := jwt_funcs.DecodeJwt(token) if err != nil { logger.ErrorLog(fmt.Sprintf("Error: decode jwt failed. Details: %v", err)) return } songs, err := redis_repo.Get(claims.Id) if err != nil { logger.ErrorLog(fmt.Sprintf("Error: get songs from redis failed. Details: %v", err)) return } for _, song := range songs { if err := download(song, conn); err != nil { logger.ErrorLog(fmt.Sprintf("Error: download song %s failed. Details: %v", song.SongId, err)) return } } } func download(song models.SongsModel, conn net.Conn) error { filePath, err := filepath.Abs(fmt.Sprintf("./songs/%s.flac", song.SongId)) if err != nil { return fmt.Errorf("create filepath failed: %w", err) } file, err := os.Open(filePath) if err != nil { return fmt.Errorf("open file failed: %w", err) } defer file.Close() return read(file, conn) } func read(file *os.File, conn net.Conn) error { buf := make([]byte, 4096) // 使用4KB缓冲区提升传输效率 for { n, err := file.Read(buf) if n > 0 { // 仅发送实际读取的n字节 if _, writeErr := conn.Write(buf[:n]); writeErr != nil { return fmt.Errorf("write to conn failed: %w", writeErr) } } if err != nil { if err == io.EOF { return nil // 文件读取完成 } return fmt.Errorf("read file failed: %w", err) } } }
客户端 main.go
func main() { conn, err := net.Dial("tcp", "127.0.0.1:6060") if err != nil { panic(fmt.Sprintf("dial failed: %v", err)) } defer conn.Close() text := "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzYzlhNmE1OWI3ZmQyNTQ2ZjA4ZWEyYSIsInVzZXJuYW1lIjoiMTIiLCJleHAiOjE2NzQyMTE5ODl9.aarSDhrFF1df3i2pIRyjNxTfSHKObqLU3kHJiPreredIhLNCzs7z7jMgRHQIcLaIvCOECN7bX0OaSvKdW7VKsQ\n" if _, err := fmt.Fprint(conn, text); err != nil { panic(fmt.Sprintf("send token failed: %v", err)) } var glMessage []byte buf := make([]byte, 4096) for { n, err := conn.Read(buf) if n > 0 { // 仅追加实际读取的n字节 glMessage = append(glMessage, buf[:n]...) } if err != nil { if err == io.EOF { break // 数据接收完成 } panic(fmt.Sprintf("read from conn failed: %v", err)) } } if err := os.WriteFile("./test.flac", glMessage, 0644); err != nil { panic(fmt.Sprintf("write file failed: %v", err)) } }
额外建议
- 缓冲区大小推荐使用4KB/8KB,过小的缓冲区会大幅降低传输效率。
- 可在传输前先发送文件大小,客户端根据大小判断接收完成,避免依赖TCP连接关闭作为结束标志,提升可靠性。
- FLAC格式对文件完整性要求极高,任何冗余或缺失字节都会导致播放器无法解析,需严格保证传输数据与源文件完全一致。
内容的提问来源于stack exchange,提问作者Cross
相关产品推荐
相关产品推荐

