Search code examples
javaimageniosocketchannel

Sending a jpeg image over a socketChannel


I'm currently testing out programming skills I'd need for a small game I'm planning to write eventually, and I'm currently stuck at transferring an image over a socket channel. I plan to test this on a "Battleship" program I wrote by sending some sort of "avatar" or "profile picture" to your opponent. I've got a working example with normal sockets:

server side:

    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();
    }

client side:

    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();

Up to this point, everything works as it should.
Now, the problems with socketChannels (Java NIO):

client side:

    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());

server side:

    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);

I haven't really worked with images so far, so there might just be some error in my usage of buffers or whatever that I can't find.
anyhow, if I execute the program (with lots of different code that works fine), I recieve an exception at the last line I provided for the server side:
javax.imageio.IIOException: Invalid JPEG file structure: missing SOS marker

any help would be greatly appreciated!


Solution

  • The biggest issue I see is the assumption that imgbuf.array() accumulates all the data. That method simply returns the array backing your buffer. Think of the buffer as a block of data, since that's what it is (ref).

    You need a full array of data that has the complete image to create it on the "server" side. So you'll have to do things a bit differently. This is not optimized code in the least, but it should help you get started:

    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 is built around blocks of data, not streams of data. It offers more throughput that way. Another option is to use a FileChannel to immediately write the buffer to a temporary file and then use a standard FileStream to read in the data--which would protect you from your app crashing because the image is too large. In that case, the loop becomes a bit more simplified:

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