Я пытаюсь создать программу с Java, которая снимает скриншот экрана пользователя, сжимает изображение и отправляет его на сервер через сокеты. По какой-то причине изображение сохраняется в конце (оно не читается). Можете ли вы помочь мне найти проблему?
КЛИЕНТ: (скриншот вводится как BufferedImage, а возвращаемый массив байтов затем возвращается ко второй функции, которая отправляет его на сервер)
public static byte[] compressImage(BufferedImage image) throws IOException {
System.out.println("starting compression");
ByteArrayOutputStream os = new ByteArrayOutputStream(37628);
float quality = 0.16f;
// create a BufferedImage as the result of decoding the supplied InputStream
// get all image writers for JPG format
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
//Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
if (!writers.hasNext())
throw new IllegalStateException("No writers found");
ImageWriter writer = (ImageWriter) writers.next();
ImageOutputStream ios = ImageIO.createImageOutputStream(os);
writer.setOutput(ios);
ImageWriteParam param = writer.getDefaultWriteParam();
// compress to a given quality
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
// appends a complete image stream containing a single image and
//associated stream and image metadata and thumbnails to the output
writer.write(null, new IIOImage(image, null, null), param);
os.flush();
return os.toByteArray();
}
public void uploadShot(byte[] imgData, String nickname) {
try {
/* Try to connect to the server on localhost, port 5555 */
Socket sk = new Socket("localhost", 23232);
OutputStream output = sk.getOutputStream();
/* Send filename to server */
OutputStreamWriter outputStream = new OutputStreamWriter(sk.getOutputStream());
outputStream.write(nickname + "\n");
outputStream.flush();
/* Get response from server */
BufferedReader inReader = new BufferedReader(new InputStreamReader(sk.getInputStream()));
String serverStatus = inReader.readLine(); // Read the first line
/* If server is ready, send the file */
if (serverStatus.equals("READY")){
int len = imgData.length;
int start = 0;
if (len < 0)
throw new IllegalArgumentException("Negative length not allowed");
if (start < 0 || start >= imgData.length)
throw new IndexOutOfBoundsException("Out of bounds: " + start);
// Other checks if needed.
// May be better to save the streams in the support class;
// just like the socket variable.
OutputStream out = sk.getOutputStream();
DataOutputStream dos = new DataOutputStream(out);
dos.writeInt(len);
if (len > 0) {
dos.write(imgData, start, len);
}
dos.close();
output.close();
sk.close();
System.out.println("Transfer complete.");
}
} catch (Exception ex){
/* Catch any errors */
System.out.println(ex.getMessage());
}
}
SERVER: (полученное изображение сохраняется в папке, указанной в метке времени)
public static void main(String args[]) throws Exception{
System.out.println("Server running...");
/* Listen on port 5555 */
ServerSocket server = new ServerSocket(23232);
/* Accept the sk */
Socket sk = server.accept();
System.out.println("Server accepted client");
InputStream input = sk.getInputStream();
BufferedReader inReader = new BufferedReader(new InputStreamReader(sk.getInputStream()));
BufferedWriter outReader = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));
/* Read the filename */
String nickname = inReader.readLine();
if ( !nickname.equals("") ){
/* Reply back to client with READY status */
outReader.write("READY\n");
outReader.flush();
}
String current = "/home/kasgel/screenshots";
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy__HH:mm:ss");
Date timestamp = new Date();
File filename = new File(current + "/" + nickname + "-" + dateFormat.format(timestamp) + ".jpg");
if (!filename.exists()) {
filename.createNewFile();
}
FileOutputStream wr = new FileOutputStream(filename);
byte[] buffer = new byte[sk.getReceiveBufferSize()];
int bytesReceived = 0;
while((bytesReceived = input.read(buffer))>0) {
wr.write(buffer,0,bytesReceived);
}
wr.close();
}
И сообщение об ошибке, которое я получаю при открытии сохраненного снимка экрана, выглядит следующим образом: display.im6: Не файл JPEG: начинается с 0x00 0x03 'MyNick-30-03-2015__19: 27: 58.jpg' @error/jpeg.c/JPEGErrorHandler/316.
Когда вы пишете свое изображение, сначала пишете 32-разрядное целое число со знаком, содержащее длину в байтах изображения:
dos.writeInt(len);
if (len > 0) {
dos.write(imgData, start, len);
}
Но когда вы читаете изображение назад, вы сначала не читаете длину; вы читаете все данные (включая длину), как если бы они были частью изображения.
Однако у вас есть вторая проблема, которая сама по себе также вызовет эту проблему. Когда вы создаете BufferedReader
и вызываете readLine
на нем, он будет читать дальше новой строки - он будет читать, пока его буфер не будет заполнен. Это не проблема, если вы продолжаете читать с нее, но после прочтения строки вы продолжаете читать из базового InputStream, у которого часто будет больше байтов, потребляемых им после новой строки.
Решение: используйте только одну абстракцию для чтения/записи данных. В этом случае проще всего использовать DataOutputStream
и DataInputStream
. Напишите имя файла, используя writeUTF
и прочитайте его с помощью readUTF
. Напишите длину файла с помощью writeInt
и прочитайте его с помощью readInt
. Напишите данные с write
и прочитайте ее с read
- и убедитесь, что вы читаете только столько байтов, сколько вы получили от вызова readInt
. И самое главное, продолжайте использовать те же DataOutputStream
и DataInputStream
; не создавать буферизованные считыватели и входные потоки в одном и том же базовом InputStream
BufferedReader
и одновременно используете базовыйInputStream
, поэтому я бы сказал, что первый «съедает» некоторые из ваших данных, поэтому содержимое, считываемое из последнего, будет лишено заголовка изображения.