You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Golang TCP的FLAC音乐文件传输播放问题求助

FLAC文件TCP传输后无法播放的问题排查与修复

你遇到的FLAC文件无法播放的核心原因是:TCP分块读写时未正确处理实际读取/写入的字节数,导致传输后的文件包含冗余无效数据,破坏了FLAC的严格文件结构。

具体问题分析

服务端代码问题

  1. 忽略Read返回的实际字节数
    在read函数中,reader.Read(buf)返回的n(实际读取的字节数)被丢弃,每次都发送整个15字节缓冲区。当文件最后一块不足15字节时,缓冲区中残留的上一次读取数据会被一并发送,导致文件末尾多出无效字节。
  2. 错误处理逻辑漏洞
    当前判断if err != nil && err == io.EOF逻辑错误:若出现非EOF的读取错误,会继续循环发送数据,导致错误扩散。
  3. 连接提前关闭
    read函数中的defer conn.Close()会在第一个歌曲传输完成后直接关闭连接,后续歌曲无法传输。

客户端代码问题

  1. 缺失错误处理
    net.Dial、reader.Read、os.WriteFile的错误被直接忽略,可能隐藏连接、读取或写入失败的问题。
  2. 追加整个缓冲区而非实际读取内容
    每次读取后直接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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 21:11:29