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!
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
}