【问题标题】:Sending a jpeg image over a socketChannel通过 socketChannel 发送 jpeg 图像
【发布时间】:2016-02-23 19:34:26
【问题描述】:

我目前正在测试我计划最终编写的小型游戏所需的编程技能,而我目前正忙于通过套接字通道传输图像。我计划通过向你的对手发送某种“头像”或“头像”来在我编写的“战舰”程序上对此进行测试。 我有一个使用普通套接字的工作示例:

服务器端:

    try {
        ServerSocket serverSocket = new ServerSocket(port); //provided at an earlier point in the code
        Socket server = serverSocket.accept();
        BufferedImage img = ImageIO.read(ImageIO.createImageInputStream(server.getInputStream()));
        //here would be code to display the image in a frame, but I left that out for readability
        server.close();
        serverSocket.close();
    } catch(Exception e) {   //shortened version to improve readability
               e.printStackTrace();
    }

客户端:

    Socket client = new Socket(ip, port);
    bimg = ImageIO.read(getClass().getResource("/images/ship_1.jpeg"));
    //the image is located at /resources/images/ship_1.jpeg
    ImageIO.write(bimg,"JPG",client.getOutputStream());
    client.close();

到目前为止,一切正常。
现在,socketChannels (Java NIO) 的问题:

客户端:

    BufferedImage bimg = ImageIO.read(getClass().getResource("/images/ship_1.jpeg"));
    ByteArrayOutputStream outputArray = new ByteArrayOutputStream();
    //i do NOT know if the following line works - System.out.println() statements after it are not executed, so ... probably doesn't work either.
    ImageIO.write(bimg, "jpeg", socketChannel.socket().getOutputStream());

服务器端:

    ByteBuffer imgbuf = ByteBuffer.allocate(40395);
    int imageBytes = socketChannel.read(imgbuf);
    while (true) {
        if (imageBytes == (0 | -1)) {
            imageBytes = socketChannel.read(imgbuf);
        } else {
            break;
        }
    }
    byte[] byteArray = imgbuf.array();
    System.out.println(byteArray.length);
    InputStream in = new ByteArrayInputStream(byteArray);
    BufferedImage img = ImageIO.read(in);

到目前为止,我还没有真正处理过图像,所以我在使用缓冲区或任何我找不到的东西时可能会出现一些错误。
无论如何,如果我执行程序(使用许多不同的代码都可以正常工作),我会在我为服务器端提供的最后一行收到一个异常:
javax.imageio.IIOException: Invalid JPEG file structure: missing SOS marker

任何帮助将不胜感激!

【问题讨论】:

  • 您是否尝试过使用ImageOutputStream 类来发送图像?
  • 通常,通过套接字发送任意大小的二进制数据的最佳方式是先发送长度,然后发送数据。这使接收者可以准确地知道预期有多少数据并采取适当的行为。
  • @jtahlborn 是的,我稍后会改变它。但是,考虑到我知道测试图像的大小,我只是在这里硬编码了大小。
  • @JonahHaney 那么我将如何编写我的代码呢?我仍在使用套接字通道:/
  • 注意:我找到了解决方案,无需编写其他答案 - 我会自己为未来的读者回答。本质上,我将图像转换为字节数组,然后将其发送到服务器。

标签: java image nio socketchannel


【解决方案1】:

大部分你都不需要。

客户端:

BufferedImage bimg = ImageIO.read(getClass().getResource("/images/ship_1.jpeg"));
ByteArrayOutputStream outputArray = new ByteArrayOutputStream();
//i do NOT know if the following line works - System.out.println() statements after it are not executed, so ... probably doesn't work either.
ImageIO.write(bimg, "jpeg", socketChannel.socket().getOutputStream());

你根本不需要ImageIO。这只是一个简单的字节拷贝:

InputStream in = getClass().getResource("/images/ship_1.jpeg");
byte[] buffer = new byte[8192];
int count;
while ((count = in.read(buffer)) > 0)
{
    socketChannel.socket().getOutputStream().write(buffer, 0, count);
}

服务器端:

ByteBuffer imgbuf = ByteBuffer.allocate(40395);
int imageBytes = socketChannel.read(imgbuf);
while (true) {
    if (imageBytes == (0 | -1)) {

这没有一点意义。它将imageBytes0 | -1 进行比较,即`0xffffffff,这只会在流结束时为真。

        imageBytes = socketChannel.read(imgbuf);

在这种情况下,再读一次是徒劳的。它只会返回另一个-1。

    } else {
        break;

因此,如果您没有得到 -1,即一旦您真正读取了一些数据,您就会崩溃。

    }
}
byte[] byteArray = imgbuf.array();
System.out.println(byteArray.length);
InputStream in = new ByteArrayInputStream(byteArray);
BufferedImage img = ImageIO.read(in);

你也不需要这些。

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteBuffer imgbuf = ByteBuffer.allocate(40395);
while ((imageBytes = socketChannel.read(imgbuf)) > 0)
{
    imgbuf.flip();
    while(imgbuf.hasRemaining())
    {
        baos.write(imgbuf.get());
    }
    imgbuf.compact();
}
BufferedImage img = ImageIO.read(new ByteArrayInputStream(baos.toByteArray()));

【讨论】:

    【解决方案2】:

    我看到的最大问题是假设imgbuf.array() 会累积所有数据。该方法仅返回支持缓冲区的数组。将缓冲区视为数据块,因为它就是这样 (ref)。

    您需要具有完整图像的完整数据数组才能在“服务器”端创建它。所以你必须做一些不同的事情。这至少不是优化的代码,但它应该可以帮助您入门:

    ArrayList<byte> fullImageData = new ArrayList<byte>();
    ByteBuffer imgbuf = ByteBuffer.allocate(40395);
    int imageBytes = socketChannel.read(imgbuf);
    
    while ((imageBytes = socketChannel.read(imgbuf)) > 0)
    {
        imgbuf.flip(); // prepare for reading
    
        while(imgbuf.hasRemaining())
        {
            fullImageData.add(imgbuf.get());
        }
    
        imgbuf.clear(); // prepare for next block
    }
    
    byte[] byteArray = fullImageData.toArray();
    System.out.println(byteArray.length);
    InputStream in = new ByteArrayInputStream(byteArray);
    BufferedImage img = ImageIO.read(in);
    

    NIO 是围绕数据块构建的,而不是数据流。它以这种方式提供更多的吞吐量。另一种选择是使用 FileChannel 立即将缓冲区写入临时文件,然后使用标准 FileStream 读取数据——这样可以防止您的应用程序因图像太大而崩溃。在这种情况下,循环变得更加简单:

    while ((imageBytes = socketChannel.read(imgbuf)) > 0)
    {
        imgbuf.flip(); // prepare for reading
    
        fileChannel.write(imgbuf); // write to temp file
    
        imgbuf.clear(); // prepare for next block
    }
    

    【讨论】:

    • 感谢您的回答,尽管我自己设法解决了问题。不过,您的描述和解释更详细-我想我只是在谷歌上找到了一种将图像转换为字节数组的更短方法^^
    • 这甚至无法编译,并且在列表中累积字节的想法是可笑的。
    • 添加到 ArrayList 与写入 ByteArrayOutputStream 有何不同?它本质上是相同的解决方案。至于不编译,我可以在有 IDE 的机器上修复代码。
    【解决方案3】:

    我最终自己找到了解决方案,首先将图像转换为字节数组,然后再通过套接字发送。
    代码:

    客户端:

        BufferedImage bimg = ImageIO.read(getClass().getResource("/images/ship_1.jpeg"));
        byte[] byteArray;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(bimg, "jpg", baos);
        baos.flush();
        byteArray = baos.toByteArray();
        baos.close();
        socketChannel.socket().getOutputStream().write(byteArray);
    

    服务器端:

    ByteBuffer imgbuf = ByteBuffer.allocate(40395);
    int imageBytes = socketChannel.read(imgbuf);
    while (true) {
        if (imageBytes == (0 | -1)) {
            imageBytes = socketChannel.read(imgbuf);
        } else {
            break;
        }
    }
    byte[] byteArray = imgbuf.array();
    System.out.println(byteArray.length);
    InputStream in = new ByteArrayInputStream(byteArray);
    BufferedImage img = ImageIO.read(in);
    

    我从here 获得了将图像转换为字节数组的代码,反之亦然,如果您有兴趣的话。

    【讨论】:

    • 您不需要客户端中的ByteArrayOutputStream。您可以使用ImageIO 直接写入套接字输出流,而不是您甚至需要它。由于我的回答中列出的原因,您的服务器端代码仍然无法正常工作。
    猜你喜欢
    • 2012-08-27
    • 2014-01-16
    • 2020-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    相关资源
    最近更新 更多