Java实现多线程远程投屏并打包可执行文件(从代码到.exe)

前言

又临近期末周了,各种PBL接踵而来。然后,就要去自主研讨室和组员开会,并与此同时发现研讨室的一体展示机并没有配数据线😥。于是,就要想尽办法将电脑投屏到一体机上去。所以,同学们就会选择去充乐播(一体机自带的投屏软件)的会员。或许,乐播的业务分析员会发现在期末月里,公司的会员数是有大幅提高的🤣。所以我就决定自己写一个投屏的软件,然后打包成exe,给研讨室的每一台一体机装上,属于是做好事不留名了😅。

原理

这个投屏软件的原理其实也是十分简单的,实现需要服务端与客户端。这两个端系统通过Socket进行连接,然后,服务端不停的进行截图操作,将这些截下来的图,转码为二进制流在传输给客户端,客户端在把二进制解码成图片,可能有同学问为什么要转为二进制流🧐,这里是因为一个截图文件过于的大,在传输的过程中,可能会造成阻塞而丢包。嗯,这就是整个过程,但是这里面还是有一些细节的。比如,对截屏的操作使用多线程线程里面,这样在截屏的过程中就不会影响到连接等。当然,这里同学们还可以改一改添加键鼠的响应,再把服务器部署到公网上就可以,实现远程操控了。😎😎

成品展示

这是两个exe文件(图标是别人的照片,打一波码😁)
在这里插入图片描述
这图标到时候打包的时候,大家想设成啥样就啥样。
点击后就会有一个对话框
这个涉及到一个方法,到时候讲解

点“是”以后,就会跳转到输入IP和端口号的界面(这个ip就是到时候服务器的区域网IP,由于我们把服务端给打包了,所以端口是固定的8888)。
在这里插入图片描述
点击确定就可以连接投屏了。
下图是电脑和Surface的投屏(随便开的一个网页哈,广告就大码了,大伙不要关注细节😅)
在这里插入图片描述
刚刚去研讨室试了一下,发现好用,
在这里插入图片描述

在这里插入图片描述
那就写个readme放那给同学们用吧😀
在这里插入图片描述
又是助人为乐的一天😂

代码实现

上面已经讲过原理了,这个小程序也不会太难,实现我们写一下他的客户端吧。

下面主要进行代码的解析,完整代码放置于其后。

客户端

生成询问框

我们看到客户端点击运行后,会有一个小对话框,这个询问框是JOptionPane中的showConfirmDialog()方法生成的

			//询问框,showConfirmDialog()方法是展现询问框
            int choice = JOptionPane.showConfirmDialog(null, "掌控对方电脑?", "霍格沃茨魔法学院秦皇岛分院", JOptionPane.YES_NO_CANCEL_OPTION);      

询问框选择响应

询问框做完后,我们要完成其选择响应,首先是点击“否(NO)”或“取消(Cancel)”按钮就退出程序。

		if (choice == JOptionPane.NO_OPTION || choice == JOptionPane.CANCEL_OPTION) {
                return;
        }

点击确定我们就要进入输入框,输入待连接的服务端的ip和端口

String input = JOptionPane.showInputDialog("请输入你要连接服务器的ip地址及端口号", "127.0.0.1:8888");

showInputDialog()方法中输入的ip和端口号是一个初始值,就同下图
在这里插入图片描述

截取输入字符串

在我们拿到服务端的ip和端口号后,我们先要把拿到的一整串字符串(包含:ip和端口)给分开

			//获取服务器的主机 substring()方法用以截取字符串
			String host = input.substring(0, input.indexOf(":"));
            //端口
            String port = input.substring(input.indexOf(":") + 1);

与服务端建立联系

分别得到ip和端口后,我们就可以建立其Socket连接服务端了

这里需要注意一点Socket()中需要的端口是Int型,而我们刚刚截下来的是String,我们可以用Integer.parseInt()方法将string包装成int

Socket client = new Socket(host, Integer.parseInt(port));

创建数据输入流

我们传送截图需要使用二进制数据流,这里就需要先写输入流,接收传送来的数据

DataInputStream dataInputStream = new DataInputStream(client.getInputStream());

创建窗口及面板

我们客户端连接到了以后是需要一个窗口,然后再在这个窗口的面板中加入传输过来的截图的,所以我们先创建一个窗口和面板,到时候把收到的截图添加进去就行。当然对于这个窗口我们也有些要求,比如,每个电脑都有不同,我们要让截图的分辨率适应窗口,以及我们为了更全的看图,就需要一个滑动滚轮等。

//创建显示面板
JFrame jFrame = new JFrame();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setTitle("任意门");
jFrame.setSize(1024,768);

//读取服务端的分辨率
double height = dataInputStream.readDouble();
double width = dataInputStream.readDouble();
Dimension ds = new Dimension((int)width,(int)height);
jFrame.setSize(ds);

//创建面板
JLabel jLabel = new JLabel();
JPanel jPanel = new JPanel();
//设置滚动条
JScrollPane jScrollPane = new JScrollPane(jPanel);
jPanel.setLayout(new FlowLayout());
jPanel.add(jLabel);
jFrame.add(jScrollPane);
jFrame.setVisible(true);
jFrame.setLocationRelativeTo(null);
jFrame.setAlwaysOnTop(true);

获取截图

现在我们就可以获取服务端发送的截图,并将截图粘贴到面板中,首先,我们需要明确的是,我们是要不断地接收这个截图,以确保实时性,同样,服务端那边也会不停的给我们发送图片,这是要放到一个循环里面。

while(true){
         //获取流的长度
         int len = dataInputStream.readInt();
         byte[] imageData = new byte[len];
         dataInputStream.readFully(imageData);
				
		 //新建一个图片,并把截图数据传递过来
         ImageIcon image = new ImageIcon(imageData);
         //再把图标放到label面板中
         jLabel.setIcon(image);
         //重新绘制面板
         jFrame.repaint();
            }

这个便是客户端的代码,编的时候大家还会发现,需要抛些异常,直接就把父类异常抛出就行了。

服务端

服务端就是核心的功能就是:建立服务器监听多线程截图以及图片转二进制数据流并流传输

建立服务器监听

这个是socket里面的内容

//建立服务器监听
ServerSocket ss = new ServerSocket(8888);
System.out.println("正在法力追踪服务器>>>");
Socket clinet = ss.accept();
System.out.println("锁定服务器成功");

建立数据流传输

OutputStream outputStream = clinet.getOutputStream();
//将文件流转换成二进制数据,用于传输
DataOutputStream doc = new DataOutputStream(outputStream);

多线程截图

进行多线程截图的原因在原理处是提到过,这是由于图片文件过于的大,在传输过程中是比较占用带宽的,这是会导致传输层拥塞而丢包。
还是先要将数据传输流定义进来

//多线程截图
class DoorThread extends Thread{

	private DataOutputStream dataout;
    public DoorThread(DataOutputStream dataout){
        this.dataout = dataout;
    }

然后,启动线程。
这里我是先说一下窗口截图是如何实现的

窗口截图实现

实现窗口截图,我们需要知道几点:

  • 1.我们用什么截图:Robot
  • 2.我们截什么图:Rectangle
  • 3.我们如何处理截图:JPEGImageEncoder

🆗,我们可以开始实践一下
截图我们要用到一个(小)机器人😕,并获取截图的区域(这个我们要使用Toolkit)

//创建一个机器人,机器人可以帮助我们截取屏幕
Robot robot = new Robot();
//获取屏幕的大小
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension dimension = toolkit.getScreenSize();

然后,我们就可以指定一个截图区域,然后再这个区域内使用Robot截图,再新建一个图片实体用来装载这个截图,再把这个截图做成图标装进Label

while (true) {
	//指定分享区域
	Rectangle rectangle = new Rectangle(jFrame.getWidth(), 0, dimension.width - jFrame.getWidth(), dimension.height);
	//截取指定区域(分享区域)内的屏幕到一张图片中
	BufferedImage bufferedImage = robot.createScreenCapture(rectangle);
	//在把图片放到一个Label里,如何再把这个label加到jFrame里就行了
	ImageLabel.setIcon(new ImageIcon(bufferedImage));
            }

上面说了一下怎么截图,现在我们回到这个工程里面,把截图这块给完成了。
和上面几乎是一样的操作,就是我们再这里要进行一步图片的压缩操作,并进行字节流传输
截图操作还是一样的

//截图
BufferedImage bufferedImage = robot.createScreenCapture(rec);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

压缩要用到JPEGImageEncoder,但是有一件比较遗憾的事是这个类,应该是说sun.image 这个类包应该是在jdk7以后就没了,需要使用别的方法代替,为了省事直接用jdk7以下版本😂

//对图片进行压缩处理
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(byteArrayOutputStream);
encoder.encode(bufferedImage);

然后,我们开启字节流传输文件流

//字节流传输的文件流
byte[] data = byteArrayOutputStream.toByteArray();
dataout.writeInt(data.length);
dataout.write(data);
dataout.flush();
Thread.sleep(0);

ok,在把这些操作套在一个线程里面,并在主方法里面启用这个多线程

//启动线程
DoorThread doorThread = new DoorThread(doc);
doorThread.start();

行了,整个程序就好了😮😮😮

完整代码

整个project是这样的
在这里插入图片描述

Client类

package cn.edu.neu.Door;

import javax.swing.*;
import java.awt.*;
import java.io.DataInputStream;
import java.net.Socket;

/**
 * 任意门的客户端
 */
public class Client {

    public static void main(String[] args) {
        try {
            //询问框,showConfirmDialog()方法是展现询问框
            int choice = JOptionPane.showConfirmDialog(null, "掌控对方电脑?", "霍格沃茨魔法学院秦皇岛分院", JOptionPane.YES_NO_CANCEL_OPTION);
            //判断点击的按钮是什么  NO_OPTION这个就是一个常量
            //如果点击了否
            if (choice == JOptionPane.NO_OPTION || choice == JOptionPane.CANCEL_OPTION) {
                return;
            }
            //输入ip地址和端口号
            String input = JOptionPane.showInputDialog("请输入你要连接服务器的ip地址及端口号", "127.0.0.1:8888");
            //获取服务器的主机 substring()方法用以截取字符串
            String host = input.substring(0, input.indexOf(":"));
            //端口
            String port = input.substring(input.indexOf(":") + 1);
            //链接服务器 Integer.parseInt()方法是将string包装成int
            Socket client = new Socket(host, Integer.parseInt(port));
            //创建输入流
            DataInputStream dataInputStream = new DataInputStream(client.getInputStream());

            //创建显示面板
            JFrame jFrame = new JFrame();
            jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jFrame.setTitle("任意门");
            jFrame.setSize(1024,768);

            //读取服务端的分辨率
            double height = dataInputStream.readDouble();
            double width = dataInputStream.readDouble();
            Dimension ds = new Dimension((int)width,(int)height);
            jFrame.setSize(ds);

            //创建面板
            JLabel jLabel = new JLabel();
            JPanel jPanel = new JPanel();
            //设置滚动条
            JScrollPane jScrollPane = new JScrollPane(jPanel);
            jPanel.setLayout(new FlowLayout());
            jPanel.add(jLabel);
            jFrame.add(jScrollPane);
            jFrame.setVisible(true);
            jFrame.setLocationRelativeTo(null);
            jFrame.setAlwaysOnTop(true);

            while(true){
                //获取流的长度
                int len = dataInputStream.readInt();
                byte[] imageData = new byte[len];
                dataInputStream.readFully(imageData);

                ImageIcon image = new ImageIcon(imageData);
                jLabel.setIcon(image);
                //重新绘制面板
                jFrame.repaint();
            }



        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Server类(包括多线程类)

package cn.edu.neu.Door;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 任意门服务类
 */
public class Server {
    public static void main(String[] args) {

        try{
            //建立服务器监听
            ServerSocket ss = new ServerSocket(8888);
            System.out.println("正在法力追踪服务器>>>");
            Socket clinet = ss.accept();
            System.out.println("锁定服务器成功");
            OutputStream outputStream = clinet.getOutputStream();
            //将文件流转换成二进制数据,用于传输
            DataOutputStream doc = new DataOutputStream(outputStream);

            //启动线程
            DoorThread doorThread = new DoorThread(doc);
            doorThread.start();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

//多线程截图
class DoorThread extends Thread{

    private DataOutputStream dataout;
    public DoorThread(DataOutputStream dataout){
        this.dataout = dataout;
    }

    //开始启动线程
    public void run() {
        //获取屏幕的大小
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Dimension dimension = toolkit.getScreenSize();
        try {
            //获取屏幕的分辨率 dataout数据输入流
            dataout.writeDouble(dimension.getHeight());
            dataout.writeDouble(dimension.getWidth());
            dataout.flush();    //刷新
            //定义分享区域大小(整个屏幕大小)
            Rectangle rec = new Rectangle(dimension);

            Robot robot = new Robot();
            while(true){
                //截图
                BufferedImage bufferedImage = robot.createScreenCapture(rec);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                //对图片进行压缩处理
                JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(byteArrayOutputStream);
                encoder.encode(bufferedImage);
                //字节流传输的文件流
                byte[] data = byteArrayOutputStream.toByteArray();
                dataout.writeInt(data.length);
                dataout.write(data);
                dataout.flush();
                Thread.sleep(0);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

程序打包

将程序打成jar包

右键点击工程文件夹
在这里插入图片描述
点击Open Module Setting
在这里插入图片描述
点击Artifacts
在这里插入图片描述
点击小加号
在这里插入图片描述
在如上图选择
在这里插入图片描述
在选择执行的主类(比如客户端,就选Client)
然后,点击OK就行了,再去输出地址找找看。
在这里插入图片描述
上面那个exe4j可以忽略,这是或许操作得到的,对了,你们现在也确实要下一个exe4j这个工具可以把jar包转为exe
下载好打开就是这样的
在这里插入图片描述
前面就按自己需求填一下
到了这里,就是最初提到的自己定义ico的事了
在这里插入图片描述
推荐一个文件格式转换在线工具(好吧你们的png,jpg转为ico😁)
在这里你可以选择程序运行载编的位数,他默认的是32位。
在这里插入图片描述
到了最关键的一步了,这里先点击绿色的小加号,把我们刚刚打的jar包添加进去,然后,再在上面选择主类
在这里插入图片描述
这里要选择可执行的Java版本,别忘了我们的JPEGImageEncode是要求jdk7以下的,所以这里max版本填7以下
在这里插入图片描述
然后,后面按需选择一下,然后到了这一步,先点击运行一下,在保存,就行了😀
在这里插入图片描述
怎么样,是挺好用的吧😁

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ultimo2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值