0

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!

PixelMaster
  • 895
  • 10
  • 28
  • Have you tried making use of the [ImageOutputStream](https://docs.oracle.com/javase/7/docs/api/javax/imageio/stream/ImageOutputStream.html) class to send the image? – Jonah Haney Feb 23 '16 at 19:43
  • typically, the best way to send arbitrarily sized binary data over a socket is to first send the length, then send the data. this allows to receiver to know exactly how much data to expect and to behave appropriately. – jtahlborn Feb 23 '16 at 19:46
  • @jtahlborn yeah, I would have changed that later. however, I just hardcoded the size here, considering that I know what the size of the test image is. – PixelMaster Feb 23 '16 at 19:48
  • @JonahHaney how exactly would I write my code then? I'm still using socket channels :/ – PixelMaster Feb 23 '16 at 19:49
  • NOTE: I found a solution, no need to write additional answers - I'll answer it myself for future readers. essentially, I converted the image to a byte array, which I then sent to the server. – PixelMaster Feb 23 '16 at 19:55

3 Answers3

1

You don't need most of this.

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

You don't need ImageIO at all for this. It's just a simple byte copy:

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

Server side:

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

This doesn't make the least bit of sense. It compares imageBytes to 0 | -1, which is `0xffffffff, which is only going to be true at end of stream.

        imageBytes = socketChannel.read(imgbuf);

in which case doing another read is futile. It will only return another -1.

    } else {
        break;

So you're breaking if you didn't get -1, i.e. as soon as you actually read some data.

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

You don't need any of this either.

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()));
user207421
  • 305,947
  • 44
  • 307
  • 483
0

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
}
Berin Loritsch
  • 11,400
  • 4
  • 30
  • 57
  • i appreciate your answer, even though I managed to solve the problem myself. Your description & explanations are more detailed, though - I guess I just found a shorter way to convert the image to a byte array on google ^^ – PixelMaster Feb 23 '16 at 20:15
  • This doesn't even compile, and the idea of accumulating bytes in a list is ludicrous. – user207421 Feb 25 '16 at 01:28
  • How is adding to an ArrayList materially different than writing to a ByteArrayOutputStream? It's essentially the same solution. As to doesn't compile, I can fix the code when I get to a machine with an IDE. – Berin Loritsch Feb 25 '16 at 18:31
-2

I eventually found the solution myself, by first converting the image to a bytearray before sending it over the socket.
code:

client side:

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

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 got the code for converting an image to a byte array and vice versa from here, in case you're interested.

PixelMaster
  • 895
  • 10
  • 28
  • You don't need the `ByteArrayOutputStream` in the client. You can use `ImageIO` to write directly to the socket output stream, not that you even need that. Your server side code still doesn't work at all, for the reason outlined in my answer. – user207421 Feb 25 '16 at 01:35