如何从Jetty服务器向RPI客户端发送字节数组?大缓冲区数据异常
问题分析与解决方案
你遇到的问题核心在于客户端的字节读取逻辑存在错误,当缓冲区较小时这个错误可能被掩盖,但缓冲区增大后,就会出现数据不完整或错误的情况。让我们一步步拆解问题并修复:
问题根源:客户端读取循环的逻辑错误
看你的客户端读取代码:
int pos = 0; byte[] b = new byte[buffLength]; while (pos != -1) { pos = is.read(b, pos, b.length - pos); } write2File(b);
这里有两个关键问题:
- 当
is.read()返回-1(表示流已读完)时,pos会被赋值为-1,此时你直接把整个b数组写入文件,但数组中只有前pos之前的有效值是正确的,后面的字节要么是初始的0,要么是之前残留的脏数据。 - 如果某次
read()返回的字节数小于剩余缓冲区长度,下一次read()会从当前pos继续填充,但一旦流结束,你没有记录实际读取的总字节数,导致写入文件时包含无效数据。
举个例子:假设缓冲区是100K,而服务端只发送了50K数据。第一次read()读取了50K,pos变成50;第二次read()返回-1,pos变成-1。此时你把整个100K数组写入文件,后面的50K都是无效的0字节,这就是你看到的"错误数据"。
修复方案:正确跟踪有效字节数并读取数据
有两种可靠的修复方式,推荐第二种(更灵活):
方式1:固定缓冲区,跟踪总读取字节数
URL url = new URL("http://localhost:1222/?filePointer=" + fp); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); // 注意:如果你没有向服务端写数据,setDoOutput(true)可以去掉,否则会默认用POST请求 // conn.setDoOutput(true); conn.connect(); InputStream is = conn.getInputStream(); System.out.println("Connection Open"); int totalRead = 0; byte[] b = new byte[buffLength]; int bytesRead; // 每次读取数据,直到返回-1 while ((bytesRead = is.read(b, totalRead, b.length - totalRead)) != -1) { totalRead += bytesRead; // 如果缓冲区满了,先写入文件,然后重置totalRead if (totalRead == b.length) { write2File(b, 0, totalRead); // 需要修改write2File方法,支持写入指定长度的字节 totalRead = 0; } } // 写入最后剩余的有效数据 if (totalRead > 0) { write2File(b, 0, totalRead); } // 记得关闭流和连接 is.close(); conn.disconnect();
这种方式需要你修改write2File方法,让它可以接收起始位置和长度参数,只写入有效字节。
方式2:使用ByteArrayOutputStream动态收集数据(更推荐)
这种方式不需要关心缓冲区大小,会自动扩容,适合任意大小的数据:
URL url = new URL("http://localhost:1222/?filePointer=" + fp); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); conn.connect(); InputStream is = conn.getInputStream(); System.out.println("Connection Open"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; // 用一个合理的小缓冲区,比如8K int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { baos.write(buffer, 0, bytesRead); } // 把收集到的有效字节数组写入文件 write2File(baos.toByteArray()); // 关闭资源 baos.close(); is.close(); conn.disconnect();
这种方式更简洁,也避免了固定缓冲区带来的问题,不管服务端发送多大的数据,都能正确接收。
服务端代码的优化建议
你的服务端代码有两个可以优化的地方:
- 不需要两次调用
stream.flush(),一次就足够(在写入数据后调用)。 - 确保输出流被正确关闭,避免客户端连接超时或资源泄漏:
ServletOutputStream stream = null; try { byte[] b = readByte(); // 你的自定义方法 stream = httpServletResponse.getOutputStream(); stream.write(b); stream.flush(); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { e.printStackTrace(); } } }
另外,如果你用的是Servlet 3.1+,可以使用try-with-resources自动关闭流:
byte[] b = readByte(); try (ServletOutputStream stream = httpServletResponse.getOutputStream()) { stream.write(b); stream.flush(); }
额外注意事项
- 如果你不需要向服务端发送数据,客户端不要设置
conn.setDoOutput(true),否则HttpURLConnection会默认使用POST请求,可能和服务端的预期不符。 - 确保
readByte()方法正确返回了所有需要发送的字节数据,没有遗漏或截断。
内容的提问来源于stack exchange,提问作者narengs7
相关产品推荐
相关产品推荐

