使用 ImageIO 发送图像后从 ImageIO 读取图像时出现未知字节
Unknown bytes while reading an image from ImageIO after sending it with ImageIO
在使用套接字通过网络传输图像时,我遇到了一个奇怪的问题:
当我使用 ImageIO.write()
将图像写入一个套接字的 OutputStream
并使用 ImageIO.read()
从另一个套接字的 InputStream
读取相同的图像时,我注意到,每个图像 16 个字节发送多于阅读。
为了能够连续发送多个图像,我必须在每次调用 ImageIO.read()
后读取这些字节才能接收到 null
,因为无法解析输入。
有谁知道,为什么会这样,这些字节是什么?
在这段代码中我提取了问题:
public class Test implements Runnable
{
public static final int COUNT = 5;
public void run()
{
try(ServerSocket server = new ServerSocket(3040))
{
Socket client = server.accept();
for(int i = 0; i < COUNT; i++)
{
final BufferedImage image = readImage(client.getInputStream());
System.out.println(image);
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
private BufferedImage readImage(InputStream stream) throws IOException
{
BufferedImage image = ImageIO.read(stream);
dontKnowWhy(stream);
return image;
}
private void dontKnowWhy(InputStream stream) throws IOException
{
stream.read(new byte[16]);
}
public static void main(String... args)
{
new Thread(new Test()).start();
try(Socket server = new Socket("localhost", 3040))
{
for(int i = 0; i < COUNT; i++)
{
BufferedImage image = new BufferedImage(300, 300, BufferedImage.TYPE_INT_ARGB); //
int[] vals = new int[image.getWidth() * image.getHeight()]; //
Arrays.fill(vals, new Random().nextInt()); // Create random image
image.setRGB(0, 0, image.getWidth(), image.getHeight(), vals, 0, 1); //
ImageIO.write(image, "png", server.getOutputStream()); //send image to server
long time = System.currentTimeMillis(); //
while(time + 1000 > System.currentTimeMillis()); //wait a second
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
我很高兴得到任何答案,已经谢谢了!
您看到的 "extra" 字节未被读取,仅仅是因为正确解码图像不需要它们(但是,很可能需要它们以所选文件格式形成完全兼容的文件, 所以它们不仅仅是随机的 "garbage" 字节)。
对于任何给定的 ImageIO
插件,读取后流中剩余的字节数可能是 0
、16
或任何其他数字。它可能取决于格式、编写它的作者、reader、输入中的图像数量、文件中的元数据等。换句话说,依赖这种行为将是错误的。
解决这个问题的简单方法是在每个图像前添加一个字节数,其中包含输出图像的长度。这通常意味着您需要将客户端的响应缓冲到 ByteArrayOutputStream
(内存中)或 FileOutputStream
(磁盘)。
然后客户端需要读取图像的字节数,并确保在读取后跳过任何剩余字节。这可以通过包装输入(参见 FilterInputStream
)并在内部跟踪字节计数来实现。
(您也可以预先读取所有字节,然后将它们包装在 ByteArrayInputStream
中,然后再将数据传递给 ImageIO.read()
,这更简单但内存缓冲更多)。
在此之后,客户端准备好重新开始,读取新的字节数和新图像。
另一种方法,如果你想在服务器上减少缓冲,可以实现类似 HTTP chunked transfer encoding 的方法,其中你有多个较小的块(块)发送到每个图像的客户端,每个块都以它自己的字节数。您需要特别处理每个图像的最后一块,或者插入特殊的分隔符块来标记流的结束或新流的开始。
下面的代码在服务器上实现了缓冲方法,同时在客户端使用直接读取。
服务器:
DataOutputStream stream = new DataOutputStream(server.getOutputStream());
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (...) {
buffer.reset();
ImageIO.write(image, "png", buffer);
stream.writeInt(buffer.size());
buffer.writeTo(stream); // Send image to server
}
客户:
DataInputStream stream = new DataInputStream(client.getInputStream());
for (...) {
int size = stream.readInt();
try (InputStream imageData = new SubStream(stream, size)) {
return ImageIO.read(imageData);
}
// Note: imageData implicitly closed using try-with-resources
}
...
// Util class
private static final class SubStream extends FilterInputStream {
private final long length;
private long pos;
public SubStream(final InputStream stream, final long length) {
super(stream);
this.length = length;
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int available() throws IOException {
return (int) Math.min(super.available(), length - pos);
}
@Override
public int read() throws IOException {
if (pos++ >= length) {
return -1;
}
return super.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (pos >= length) {
return -1;
}
int count = super.read(b, off, (int) Math.min(len, length - pos));
if (count < 0) {
return -1;
}
pos += count;
return count;
}
@Override
public long skip(long n) throws IOException {
if (pos >= length) {
return -1;
}
long skipped = super.skip(Math.min(n, length - pos));
if (skipped < 0) {
return -1;
}
pos += skipped;
return skipped;
}
@Override
public void close() throws IOException {
// Don't close wrapped stream, just consume any bytes left
while (pos < length) {
skip(length - pos);
}
}
}
在使用套接字通过网络传输图像时,我遇到了一个奇怪的问题:
当我使用 ImageIO.write()
将图像写入一个套接字的 OutputStream
并使用 ImageIO.read()
从另一个套接字的 InputStream
读取相同的图像时,我注意到,每个图像 16 个字节发送多于阅读。
为了能够连续发送多个图像,我必须在每次调用 ImageIO.read()
后读取这些字节才能接收到 null
,因为无法解析输入。
有谁知道,为什么会这样,这些字节是什么?
在这段代码中我提取了问题:
public class Test implements Runnable
{
public static final int COUNT = 5;
public void run()
{
try(ServerSocket server = new ServerSocket(3040))
{
Socket client = server.accept();
for(int i = 0; i < COUNT; i++)
{
final BufferedImage image = readImage(client.getInputStream());
System.out.println(image);
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
private BufferedImage readImage(InputStream stream) throws IOException
{
BufferedImage image = ImageIO.read(stream);
dontKnowWhy(stream);
return image;
}
private void dontKnowWhy(InputStream stream) throws IOException
{
stream.read(new byte[16]);
}
public static void main(String... args)
{
new Thread(new Test()).start();
try(Socket server = new Socket("localhost", 3040))
{
for(int i = 0; i < COUNT; i++)
{
BufferedImage image = new BufferedImage(300, 300, BufferedImage.TYPE_INT_ARGB); //
int[] vals = new int[image.getWidth() * image.getHeight()]; //
Arrays.fill(vals, new Random().nextInt()); // Create random image
image.setRGB(0, 0, image.getWidth(), image.getHeight(), vals, 0, 1); //
ImageIO.write(image, "png", server.getOutputStream()); //send image to server
long time = System.currentTimeMillis(); //
while(time + 1000 > System.currentTimeMillis()); //wait a second
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
我很高兴得到任何答案,已经谢谢了!
您看到的 "extra" 字节未被读取,仅仅是因为正确解码图像不需要它们(但是,很可能需要它们以所选文件格式形成完全兼容的文件, 所以它们不仅仅是随机的 "garbage" 字节)。
对于任何给定的 ImageIO
插件,读取后流中剩余的字节数可能是 0
、16
或任何其他数字。它可能取决于格式、编写它的作者、reader、输入中的图像数量、文件中的元数据等。换句话说,依赖这种行为将是错误的。
解决这个问题的简单方法是在每个图像前添加一个字节数,其中包含输出图像的长度。这通常意味着您需要将客户端的响应缓冲到 ByteArrayOutputStream
(内存中)或 FileOutputStream
(磁盘)。
然后客户端需要读取图像的字节数,并确保在读取后跳过任何剩余字节。这可以通过包装输入(参见 FilterInputStream
)并在内部跟踪字节计数来实现。
(您也可以预先读取所有字节,然后将它们包装在 ByteArrayInputStream
中,然后再将数据传递给 ImageIO.read()
,这更简单但内存缓冲更多)。
在此之后,客户端准备好重新开始,读取新的字节数和新图像。
另一种方法,如果你想在服务器上减少缓冲,可以实现类似 HTTP chunked transfer encoding 的方法,其中你有多个较小的块(块)发送到每个图像的客户端,每个块都以它自己的字节数。您需要特别处理每个图像的最后一块,或者插入特殊的分隔符块来标记流的结束或新流的开始。
下面的代码在服务器上实现了缓冲方法,同时在客户端使用直接读取。
服务器:
DataOutputStream stream = new DataOutputStream(server.getOutputStream());
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (...) {
buffer.reset();
ImageIO.write(image, "png", buffer);
stream.writeInt(buffer.size());
buffer.writeTo(stream); // Send image to server
}
客户:
DataInputStream stream = new DataInputStream(client.getInputStream());
for (...) {
int size = stream.readInt();
try (InputStream imageData = new SubStream(stream, size)) {
return ImageIO.read(imageData);
}
// Note: imageData implicitly closed using try-with-resources
}
...
// Util class
private static final class SubStream extends FilterInputStream {
private final long length;
private long pos;
public SubStream(final InputStream stream, final long length) {
super(stream);
this.length = length;
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int available() throws IOException {
return (int) Math.min(super.available(), length - pos);
}
@Override
public int read() throws IOException {
if (pos++ >= length) {
return -1;
}
return super.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (pos >= length) {
return -1;
}
int count = super.read(b, off, (int) Math.min(len, length - pos));
if (count < 0) {
return -1;
}
pos += count;
return count;
}
@Override
public long skip(long n) throws IOException {
if (pos >= length) {
return -1;
}
long skipped = super.skip(Math.min(n, length - pos));
if (skipped < 0) {
return -1;
}
pos += skipped;
return skipped;
}
@Override
public void close() throws IOException {
// Don't close wrapped stream, just consume any bytes left
while (pos < length) {
skip(length - pos);
}
}
}