Java 使用ImageIO发送图像后,从ImageIO读取图像时出现未知字节

Java 使用ImageIO发送图像后,从ImageIO读取图像时出现未知字节,java,image,sockets,javax.imageio,Java,Image,Sockets,Javax.imageio,在使用套接字通过网络传输图像时,我遇到了一个奇怪的问题: 当我用ImageIO.write()将图像写入一个套接字的OutputStream中,并从另一个用ImageIO.read()的套接字的InputStream中读取相同的图像时,我注意到,每个图像发送的字节数超过读取的字节数。 为了能够连续发送多个图像,我必须在每次调用ImageIO.read()后读取这些字节,才能不接收null,因为无法解析输入 有人知道为什么会这样,这些字节是什么吗 在这段代码中,我提取了问题: public cla

在使用套接字通过网络传输图像时,我遇到了一个奇怪的问题:
当我用
ImageIO.write()
将图像写入一个套接字的
OutputStream
中,并从另一个用
ImageIO.read()
的套接字的
InputStream
中读取相同的图像时,我注意到,每个图像发送的字节数超过读取的字节数。 为了能够连续发送多个图像,我必须在每次调用
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();
        }

    }
}
公共类测试实现可运行
{
公共静态最终整数计数=5;
公开募捐
{
try(serversocketserver=newserversocket(3040))
{
Socket client=server.accept();
for(int i=0;iSystem.currentTimeMillis());//请稍等
}
}
捕获(IOE异常)
{
e、 printStackTrace();
}
}
}
我很高兴有任何答案,已经谢谢你了

您看到的“额外”字节不会被读取,这仅仅是因为不需要它们来正确解码图像(但是,它们很可能是以所选文件格式形成完全兼容文件所必需的,因此它们不仅仅是随机的“垃圾”字节)

对于任何给定的
ImageIO
插件,读取后流中剩余的字节数可以是
0
16
或任何其他数字。它可能取决于格式、编写它的作者、读取器、输入中的图像数量、文件中的元数据等。换句话说,依赖此行为将是一个错误

解决这个问题的简单方法是在每个图像前面加一个字节计数,包含输出图像的长度。这通常意味着您需要将客户端上的响应缓冲到
ByteArrayOutputStream
(内存中)或
FileOutputStream
(磁盘)

然后,客户端需要读取图像的字节计数,并确保在读取后跳过任何剩余字节。这可以通过包装输入(请参见FilterInputStream)并在内部跟踪字节计数来实现

(在将数据传递到
ImageIO.read()
之前,您还可以预先读取所有字节,并将它们包装在
ByteArrayInputStream
中,这样做更简单,但内存缓冲更多)

在此之后,客户机准备好重新开始,读取一个新的字节计数和一个新的映像

如果您希望减少服务器上的缓冲,另一种方法可能是实现这样的功能,即为每个映像向客户端发送多个较小的块(chunk),每个块前面都有自己的字节计数。您需要特别处理每个图像的最后一个块,或者插入特殊的分隔符块来标记流的结束或新流的开始

下面的代码在服务器上实现了缓冲方法,同时在客户机上使用直接读取

服务器:

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类
私有静态最终类子流扩展FilterInputStream{
私人最终长;
私人长pos;
公共子流(最终输入流,最终长长度){
超级(流);
这个长度=长度;
}
@凌驾
公共布尔标记受支持(){
返回false;
}
@凌驾
public int available()引发IOException{
return(int)Math.min(super.available(),length-pos);
}
@凌驾
public int read()引发IOException{
如果(pos++>=长度){
返回-1;
}
返回super.read();
}
@凌驾
公共整数读取(字节[]b,整数关闭,整数长度)引发IOException{
如果(位置>=长度){
返回-1;
}
int count=super.read(b,off,(int)Math.min(len,length-pos));
如果(计数<0){
返回-1;
}
pos+=计数;
返回计数;
}
@凌驾
公共长跳过(长n)引发IOException{
如果(位置>=长度){
返回-1;
}
long skipped=super.skip(Math.min(n,length-pos));
如果(跳过<0){
返回-1;
}
pos+=跳过;
返回跳过;
}
@凌驾
public void close()引发IOException{
//不要关门
// 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);
        }
    }
}