讲师中心 微信公众号
AI工具推荐 视频效率加速

Java Socket 文件传输中因未校验剩余字节数导致内容重复的解决方案

老宇姑娘_7906

老宇姑娘_7906

发布时间:2026-02-12 10:30:06

|

202人浏览过

|

来源于php中文网

原创

Java Socket 文件传输中因未校验剩余字节数导致内容重复的解决方案

本文详解 java 原生 socket 文件传输时内容重复的根本原因——客户端未严格按预发文件长度终止读取循环,导致缓冲区残留数据被重复写入;并提供健壮、无额外依赖的修复方案。

在使用 Java 原生 DataInputStream/DataOutputStream 实现 Socket 文件传输时,一个隐蔽但高频的问题是:接收端文件内容出现重复(如末尾多出前 N 字节)。问题并非源于 ObjectOutputStream 缺失,也不是网络粘包(TCP 本身无消息边界),而在于发送与接收逻辑对“文件结束”的判定不一致。

核心症结在于:InputStream.read(byte[], int, int) 方法在到达流末尾时返回 -1,但当文件大小不能被缓冲区整除时(例如文件长 2050 字节,缓冲区为 1024 字节),最后一次 read() 可能只读取 2 字节,随后 size 仍 > 0,循环继续——此时 read() 再次调用将阻塞或(在非阻塞模式下)返回 -1,但若未检查该返回值就直接 write(buffer, 0, bytes),就会把上一次读取的旧数据(如前 1024 字节)再次写出,造成重复。

原客户端代码中这一关键缺陷体现在:

while(size > 0 && (bytes = input.read(buffer,0,(int)Math.min(buffer.length, size)))!=-1)
{
    out.write(buffer, 0, bytes); // ✅ 正确:用实际读取字节数
    out.flush();
    size -= bytes;
}

看似合理,但问题在于:当 size > 0 但 input.read() 返回 -1(即流已关闭或异常)时,循环条件虽退出,但若服务端未及时关闭连接,客户端可能因网络延迟等原因,在下次迭代前 read() 仍返回 -1,而 bytes 保持上一轮值(如 1024),导致 out.write(buffer, 0, 1024) 写入陈旧数据。更本质的是:size > 0 仅表示“预期还有数据”,不能替代对 read() 返回值的严格校验。

立即学习“Java免费学习笔记(深入)”;

JavaStoryDevelop
JavaStoryDevelop

你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在

下载

✅ 正确做法是:以 read() 返回值为唯一 EOF 判定依据,size 仅用于限制单次读取上限,不参与循环控制。修复后的客户端 sendFile 方法如下:

private void sendFile(String path) {
    File file = new File(path);
    try (FileInputStream input = new FileInputStream(file);
         DataOutputStream out = this.out) { // 复用已有 out,确保流一致性

        long fileSize = file.length();
        System.out.println("Sending file size: " + fileSize);
        out.writeLong(fileSize); // 先发送文件总长度
        out.flush();

        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = input.read(buffer)) != -1) { // ✅ 核心:仅靠 read() 返回值判断 EOF
            // 优化:避免发送超出剩余长度的数据(可选,提升安全性)
            int toWrite = (int) Math.min(bytesRead, fileSize);
            out.write(buffer, 0, toWrite);
            out.flush();
            fileSize -= toWrite;

            // 防止 fileSize 为负(理论不应发生,但增强鲁棒性)
            if (fileSize < 0) {
                throw new IOException("File size mismatch: attempted to write more than declared length");
            }
        }
        System.out.println("File sent successfully.");

    } catch (IOException e) {
        e.printStackTrace();
    }
}

服务端 receiveFile 同理需修正循环逻辑,移除 size > 0 的联合判断,仅依赖 in.read() 返回值:

private void receiveFile(String path) {
    try (FileOutputStream output = new FileOutputStream(path)) {
        long remaining = in.readLong(); // 读取声明的文件长度
        System.out.println("Expecting " + remaining + " bytes");

        byte[] buffer = new byte[1024];
        int bytesRead;
        while (remaining > 0 && (bytesRead = in.read(buffer, 0, 
                (int) Math.min(buffer.length, remaining))) != -1) {

            output.write(buffer, 0, bytesRead);
            remaining -= bytesRead;
        }

        // ✅ 关键补充:校验是否完整接收
        if (remaining != 0) {
            throw new IOException("Incomplete file transfer: expected " + 
                (remaining + (int)(in.readLong() - remaining)) + 
                " bytes, but received only " + (int)(in.readLong() - remaining));
        }
        System.out.println("File received successfully.");

    } catch (IOException e) {
        e.printStackTrace();
    }
}

⚠️ 重要注意事项:

  • 资源管理:务必使用 try-with-resources 确保 FileInputStream/FileOutputStream 在异常时自动关闭,避免句柄泄漏。
  • flush() 调用:DataOutputStream 的 write*() 方法不自动 flush,必须显式调用 flush() 保证数据及时发出(尤其小文件)。
  • 缓冲区安全:Math.min(buffer.length, remaining) 防止 read() 尝试读取超过剩余长度,避免潜在越界(尽管 read() 本身安全,但体现严谨性)。
  • 服务端健壮性:增加 remaining == 0 校验,若提前读完却仍有数据,应视为协议错误而非静默忽略。
  • 字符打印调试:示例中 System.out.print((char)b) 仅用于调试,实际生产环境应避免,因二进制文件含非 UTF-8 字节会抛 StringIndexOutOfBoundsException 或显示乱码。

总结:Socket 文件传输重复的本质,是混淆了“业务层长度声明”与“传输层流状态”。永远以 InputStream.read() 的返回值 -1 作为 EOF 的唯一权威信号,file.length() 仅作预分配和校验用途。此原则适用于所有基于流的协议设计,无需引入 ObjectStream,即可实现简洁、可靠、零依赖的文件传输。

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

相关专题

更多
python中print函数的用法
python中print函数的用法

python中print函数的语法是“print(value1, value2, ..., sep=' ', end=' ', file=sys.stdout, flush=False)”。本专题为大家提供print相关的文章、下载、课程内容,供大家免费下载体验。

3020

2023.09.27

python print用法与作用
python print用法与作用

本专题整合了python print的用法、作用、函数功能相关内容,阅读专题下面的文章了解更多详细教程。

249

2026.02.03

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

6639

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3205

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

4148

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2845

2025.08.29

length函数用法
length函数用法

length函数用于返回指定字符串的字符数或字节数。可以用于计算字符串的长度,以便在查询和处理字符串数据时进行操作和判断。 需要注意的是length函数计算的是字符串的字符数,而不是字节数。对于多字节字符集,一个字符可能由多个字节组成。因此,length函数在计算字符串长度时会将多字节字符作为一个字符来计算。更多关于length函数的用法,大家可以阅读本专题下面的文章。

1887

2023.09.19

点击input框没有光标怎么办
点击input框没有光标怎么办

点击input框没有光标的解决办法:1、确认输入框焦点;2、清除浏览器缓存;3、更新浏览器;4、使用JavaScript;5、检查硬件设备;6、检查输入框属性;7、调试JavaScript代码;8、检查页面其他元素;9、考虑浏览器兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

854

2023.11.24

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

60

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn