Java通过套接字发送屏幕截图

3

我正在制作一个恶作剧程序,但实际上它还有一定的用处。

目前我正在尝试从客户端获取屏幕截图,并在服务器上将截图作为JFrame打开。

经过多次尝试,我发现了这个未解决的问题:如何在Java中通过套接字发送图像?

屏幕截图传输成功,但是被切割了,只有大约120-135kb的数据传输成��。在最受欢迎的答案中,他在客户端上使用了Thread.sleep()在关闭连接之前,但是我尝试了没有它,结果也被切割了,所以我按照他的方式尝试了一下,但仍然被切割了。我的客户端类读取输入数据时循环运行每100毫秒一次,所以我认为它循环得太快了,所以我将其增加到1000毫秒,但仍然被切割了。

我在同一线程上尝试了另一种不同的方法,并在服务器端将ImageIO.read(new ByteArrayInputStream(imageAr))更改为ImageIO.read(new ByteArrayInputStream(imageAr)),但那次没有传输任何图像。

这是我开始尝试时尝试的原始方法之一,但根本不起作用!https://javabelazy.blogspot.com/2013/10/sending-screenshot-from-client-to.html

服务器

package UI;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class ViewScreen implements Runnable{
private static Thread t;
@Override
public void run(){
    //Screenshot Frame
    final JFrame frame = new JFrame();
    frame.setSize(500, 500);
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    t = new Thread(){
        public void run(){
            try{
                int port = 6667;
                ServerSocket serverSocket = new ServerSocket(port);
                Socket server = serverSocket.accept();
                TrojanUI.console.append("[*]Waiting for screenshot on port " + serverSocket.getLocalPort() + "...\n");
                DataInputStream in = new DataInputStream(server.getInputStream());
                DataOutputStream out = new DataOutputStream(server.getOutputStream());
                byte[] sizeAr = new byte[4];
                in.read(sizeAr);
                int size = ByteBuffer.wrap(sizeAr).asIntBuffer().get();

                byte[] imageAr = new byte[size];
                in.read(imageAr);

                BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageAr));
                ImageIO.write(image, "jpg", new File("screen.jpg"));
                server.close();
                JLabel label = new JLabel();
                label.setIcon(new ImageIcon(image));
                frame.getContentPane().add(label);
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    };
    t.start();
    frame.setVisible(true);
}
}

这部分服务器代码是一个新线程,创建了一个新的socket和一个新的JFrame来显示屏幕截图。为什么要这样做呢?在另一个线程中,我读到只有当socket关闭时才会发送图像。因此,由于我不希望主socket关闭,否则就会失去连接,我创建了另一个socket,它将临时打开,仅用于流式传输屏幕截图。

客户端

package tc;

import java.awt.AWTException;
import java.awt.HeadlessException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import javax.imageio.ImageIO;

public class SendScreen{
public SendScreen(){
    try{
        //creates new socket on port 6667
        Socket sclient = new Socket("192.168.0.5",6667);
        OutputStream sOutToServer = sclient.getOutputStream();
        DataOutputStream out = new DataOutputStream(sOutToServer);
        InputStream sInFromServer = sclient.getInputStream();
        DataInputStream in = new DataInputStream(sInFromServer);

        //Takes screenshot and sends it to server as byte array
        BufferedImage screencap = new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(screencap,"jpg",baos);

        //Gets screenshot byte size and sends it to server and flushes then closes the connection
        byte[] size = ByteBuffer.allocate(4).putInt(baos.size()).array();
        out.write(size);
        out.write(baos.toByteArray());
        out.flush();
        sclient.close();
    }catch(HeadlessException | AWTException | IOException e){
        e.printStackTrace();
    }
}
}

在服务器端,我添加了另一行代码来在本地创建图像,以便排查是否有任何问题出现。

提前感谢。

1个回答

3
您认为read()会填充缓冲区,但是规范中并没有这样的说明。可以尝试以下代码:
int size = in.readInt();
byte[] imageAr = new byte[size];
in.readFully(imageAr);

readInt()readFully()确保读取正确的数量,否则会在尝试时失败。

在发送端也会遇到困难。请尝试以下方法:

out.writeInt(size);
out.write(baos.toByteArray());
out.close();

注意在调用close()之前调用flush()是多余的,但应该关闭包围套接字而不是套接字本身的最外层输出流,以使其自动刷新。
实际上,由于每个图像后都会关闭连接,因此可以摆脱大小单词和字节数组输入/输出流,并直接从/向套接字使用ImageIO.read/write。这将节省大量内存。

我从这个更改中得到了明显更好的结果。实际上,我在发送端保留了size作为字节数组,在接收端也是如此,我捕获了大部分屏幕,但右侧和底部的屏幕没有被捕获。这可能与我使用RDP进行远程编程有关。非常感谢! - xR34P3Rx
我仍然会删除大小词和字节数组流,因为它们会占用空间和时间成本。不要把简单的事情复杂化。 - user207421
嘿嘿... @EJP,我可以问个问题吗?out.writeInt(size); out.write(baos.toByteArray()); out.close();对我来说很好。除非我得到了size的值,我从哪里得到它呢?是图像的int大小还是其他什么? - gumuruh
@gumuruh,你可以从baos.size()获取它,就像原始帖子中的代码一样。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接