- 前言
- 原理
- 成品展示
- 代码实现
- 客户端
- 生成询问框
- 询问框选择响应
- 截取输入字符串
- 与服务端建立联系
- 创建数据输入流
- 创建窗口及面板
- 获取截图
- 服务端
- 建立服务器监听
- 建立数据流传输
- 多线程截图
- 窗口截图实现
- 完整代码
- ==Client类==
- ==Server类(包括多线程类)==
- 程序打包
- 将程序打成jar包
又临近期末周了,各种PBL接踵而来。然后,就要去自主研讨室和组员开会,并与此同时发现研讨室的一体展示机并没有配数据线。于是,就要想尽办法将电脑投屏到一体机上去。所以,同学们就会选择去充乐播(一体机自带的投屏软件)的会员。或许,乐播的业务分析员会发现在期末月里,公司的会员数是有大幅提高的藍。所以我就决定自己写一个投屏的软件,然后打包成exe,给研讨室的每一台一体机装上,属于是做好事不留名了。
原理这个投屏软件的原理其实也是十分简单的,实现需要服务端与客户端。这两个端系统通过Socket进行连接,然后,服务端不停的进行截图 *** 作,将这些截下来的图,转码为二进制流在传输给客户端,客户端在把二进制解码成图片,可能有同学问为什么要转为二进制流類,这里是因为一个截图文件过于的大,在传输的过程中,可能会造成阻塞而丢包。嗯,这就是整个过程,但是这里面还是有一些细节的。比如,对截屏的 *** 作使用多线程线程里面,这样在截屏的过程中就不会影响到连接等。当然,这里同学们还可以改一改添加键鼠的响应,再把服务器部署到公网上就可以,实现远程 *** 控了。
成品展示这是两个exe文件(图标是别人的照片,打一波码)
这图标到时候打包的时候,大家想设成啥样就啥样。
点击后就会有一个对话框
这个涉及到一个方法,到时候讲解
点“是”以后,就会跳转到输入IP和端口号的界面(这个ip就是到时候服务器的区域网IP,由于我们把服务端给打包了,所以端口是固定的8888)。
点击确定就可以连接投屏了。
下图是电脑和Surface的投屏(随便开的一个网页哈,广告就大码了,大伙不要关注细节)
刚刚去研讨室试了一下,发现好用,
那就写个readme放那给同学们用吧
又是助人为乐的一天
上面已经讲过原理了,这个小程序也不会太难,实现我们写一下他的客户端吧。
客户端 生成询问框下面主要进行代码的解析,完整代码放置于其后。
我们看到客户端点击运行后,会有一个小对话框,这个询问框是JOptionPane中的showConfirmDialog()方法生成的
//询问框,showConfirmDialog()方法是展现询问框 int choice = JOptionPane.show/confirm/iDialog(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是这样的
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.show/confirm/iDialog(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以下
然后,后面按需选择一下,然后到了这一步,先点击运行一下,在保存,就行了
怎么样,是挺好用的吧
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)