屏幕监视设计思想一(理论型--初步设计):

1.用Java中自带的截屏方法进行截屏,然后生成图片,保存在本地(比如C://temp//1.jpg)主机上(被监视端)

2.用socket将本地的图片文件传送到指定的电脑上(监视端),并且保存在该电脑上(比如C://temp//1.jpg)

3.用Java的swing写出一个显示图片的窗体,在监视端的电脑上运行并且将传送过来的图片(比如C://temp//1.jpg)显示出来。

     以上三个步骤是最原始的屏幕监视设计思路,然后我们再将以上三个步骤循环的有序执行,

     监视端显示图片的窗体再不停的刷新,我们就可以看到了被视端电脑屏幕的实时变化了,从而达到监视的效果。


  优点:

 1.设计起来比较简单,并且比较容易理解,比较适合初级程序员设计。

  缺点:

 1.在磁盘中产生了大量的图片文件,该文件很快就变成了垃圾文件,不适合应用到现实中。

 2.如果被监视端的电脑处于静止状态(键鼠空闲),该循环仍在执行,性能上会有很大的浪费

 3.需要很好的网速支持,为了达到比较清晰的效果,循环频率应该比较高些(比如每秒50次),只能在局域网中使用。

 


屏幕监视设计思想二(优化型--利用内存):

1.用Java中自带的截屏方法进行截屏,然后将截取的屏幕图片对象保存到一个图片对象里面(内存)

2.用socket将该图片对象传送到指定的电脑上(监视端)

3.用Java的swing写出一个显示图片的窗体,在监控端的电脑上运行并且将该图片对象显示出来。


  优点:

 1.设计起来稍有难度,比较适合对socket有一定了解的程序员设计。

 2.没有生成图片的这个过程了,避免垃圾文件的产生。

  缺点:

 2.如果被监视端的电脑处于静止状态(键鼠空闲),该循环仍在执行,性能上会有很大的浪费

 1.需要很好的网速支持,为了达到比较清晰的效果,循环频率应该比较高些(比如每秒50次),只能在局域网中使用。


屏幕监视设计思想三(高级型--运动检测):

1.首先对被视端电脑的鼠标和键盘添加上监听。

2.运动分析:

 1.当发现鼠标移动时(从开始移动到停止移动)以一定的循环次数(比如50次)执行设计思想二中的流程。

 2.当发现鼠标单击时(从右键或左键按下到起来)以一定的循环次数(比如50次)执行设计思想二中的流程。

 3.当发现鼠标拖动时(从开始移动到停止移动)以一定的循环次数(比如50次)执行设计思想二中的流程。


 4.当发现键盘点击时(从开始按下到起来)以一定的循环次数(比如50次)执行设计思想二中的流程。


  优点:

 1.设计起来比较难度,而且最大的难度是Java自身不支持后台监听(仅能对鼠标做一定的监听,但并不能实现全面监听),

  所以要借助与JNI(Java调用C语言,用C语言写出后台监听),比较适合对高级程序员设计。

 2.没有生成图片的这个过程了,避免垃圾文件的产生。

 3.当被监视端的电脑处于静止状态(键鼠空闲)时,监视过程停止,在性能上会有很大的提高。

  缺点:

 1.需要很好的网速支持,为了达到比较清晰的效果,循环频率应该比较高些(比如每秒50次),只能在局域网中使用。



以上三种设计思想是我们想到并且总结出来的。或许并不是很好,仅供参考,如有比较好的设计方案多谢推荐。

我们在黑天鹅远控中使用的是第二种设计思想。


其实我一直还是很佩服"凌波多媒体教学"软件的设计,该软件可以将一台电脑的屏幕图像同时显示到几十台电脑上,而且也并不是很卡。

不知他们的屏幕监视设计原理是什么。


当屏幕监视设计出来之后,屏幕监控就很简单了。需要在监控端看到被控端屏幕的基础上,

再添加上将监控端的鼠标和键盘的操作传送到被控端,并且执行就可以了。

import java.awt.AWTException;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.imageio.ImageIO;

/**
 *使用Java截屏工具,不停的截取当前屏幕图片,图片不需保存直接以流的形式发送的监控端电脑上,并显示出来
  *控制端的鼠标和键盘的操作再发送的被控端并且执行从而实现屏幕监控
  *可以达到用一台电脑完全控制另外一台电脑
  */

public class Server{
	public static void main(String args[]) {
  SendScreenImg sender=new SendScreenImg();
  sender.changeServerPort(30009);//此处可以修改服务端口
  new Thread(sender).start();//打开图像传输服务
  OperateWindow operate=new OperateWindow();
//  operate.changeServerPort(30010);//此处可以修改服务端口
  new Thread(operate).start();//打开主机操控服务
  
  //***** 当然 服务器端的端口修改是随时都可以操作的 它实际上是关闭以前的端口 再开启一个新端口 *****//
	}
}

/**
 * @author LanXJ @doctime 2010-7-8
 * 开启一个设定端口的服务,该服务用于向客户端传送主机的屏幕信息,实现客户端对服务器端主机的监控
 * 实例化线程类后默认打开DEFAULT_SERVER_PORT=30011 端口实现监听
 * 可以通过changeServerPort改变监听端口,也可以通过getServerPort来查询当前监听端口
 */
class SendScreenImg implements Runnable{

	public static final int DEFAULT_SERVER_PORT=30011;
	private int serverPort;
	private Robot robot;
	private ServerSocket serverSocket;
	private Rectangle rect;
	private Dimension screen;
	private BufferedImage img;
	private Socket socket;
	private ZipOutputStream zip;

	public SendScreenImg() {
  this.serverPort=SendScreenImg.DEFAULT_SERVER_PORT;

  try {
  	serverSocket = new ServerSocket(this.serverPort);
  	serverSocket.setSoTimeout(86400000);
  } catch (IOException e1) {
  	e1.printStackTrace();
  }

  try {
  	robot = new Robot();
  } catch (AWTException e) {
  	e.printStackTrace();
  }
  screen = Toolkit.getDefaultToolkit().getScreenSize();
  rect = new Rectangle(screen);

	}
	public void changeServerPort(int serverPort){
  if(this.serverPort==serverPort)return;
  this.serverPort=serverPort;
  try {
  	this.serverSocket.close();
  } catch (Exception e) {}
  try {
  	serverSocket = new ServerSocket(this.serverPort);
  	serverSocket.setSoTimeout(86400000);
  } catch (IOException e1) {
  	e1.printStackTrace();
  }
	}
	public int getServerPort(){
  return this.serverPort;
	}

	public void run() {
  while (true) {
             try {
              System.out.println("等待接收截屏信息");
                 socket = serverSocket.accept();
                 zip = new ZipOutputStream(new DataOutputStream(socket.getOutputStream()));
                 zip.setLevel(9);//为后续的 DEFLATED 条目设置压缩级别 压缩级别 (0-9)
                 try {
                  img = robot.createScreenCapture(rect);
                     zip.putNextEntry(new ZipEntry("test.jpg"));
                     ImageIO.write(img, "jpg", zip);
                     if(zip!=null)zip.close();
                     System.out.println("被控端:connect");
                 } catch (IOException ioe) {
                     System.out.println("被控端:disconnect");
                 }
             } catch (IOException ioe) {
              System.out.println("错误1");
             } finally {
                 if (socket != null) {
                     try {
                         socket.close();
                     } catch (IOException e) {
                     }
                 }
             }
         }
	}
}


/**
 * @author LanXJ @doctime 2010-7-8
 * 开启一个设定端口的服务,该服务用于接受客户端传来的操作字符串,实现对服务器端主机的操控
 * 实例化线程类后默认打开DEFAULT_SERVER_PORT=30012 端口实现监听
 * 可以通过changeServerPort改变监听端口,也可以通过getServerPort来查询当前监听端口
 */
class OperateWindow implements Runnable {
	public static final int DEFAULT_SERVER_PORT=30012;
	private int serverPort;
	private ServerSocket serverSocket;
	private Robot robot;
	public OperateWindow() {
  this.serverPort=OperateWindow.DEFAULT_SERVER_PORT;
  try {
  	this.serverSocket = new ServerSocket(this.serverPort);
  	this.serverSocket.setSoTimeout(86400000);
  } catch (IOException e) {
  	e.printStackTrace();
  }
  try {
  	robot = new Robot();
  } catch (AWTException e) {
  	e.printStackTrace();
  }
	}
	public void changeServerPort(int serverPort){
  if(this.serverPort==serverPort)return;
  this.serverPort=serverPort;
  try {
  	this.serverSocket.close();
  } catch (Exception e) {}
  try {
  	this.serverSocket = new ServerSocket(this.serverPort);
  	this.serverSocket.setSoTimeout(86400000);
  } catch (Exception e) {
  	e.printStackTrace();
  }
	}
	public int getServerPort(){
  return this.serverPort;
	}

	public void run() {
  while (true) {
  	try {
    Socket socket = serverSocket.accept();
    //读取操作信息:120,200,InputEvent.BUTTON1_DOWN_MASK 全部是int类型
    InputStream is = socket.getInputStream();
    int r;
    String info = "";
    while ((r = is.read()) != -1) {
    	info += "" + (char) r;
    }
    System.out.println(info);
    is.close();
    if (info != null) {
    	String s[] = info.trim().split(",");
    	if ("mouseClicked".equals(s[0].trim())) {//operateStr Model: mouseClicked,x,y,type  
      //由于加上单击事件后,鼠标按下并快速抬起 就设计到按下、抬起、单击 三个事件,将单击变为了双击 不合乎规范  所以 服务端并没有实现单击事件的监听,这里保留 不坐修改
      int type = Integer.parseInt(s[s.length - 1].trim());
      if (s.length == 4) {
      	int x = Integer.parseInt(s[1].trim());
      	int y = Integer.parseInt(s[2].trim());
      	robot.mouseMove(x, y);
      	robot.mousePress(type);
      	robot.mouseRelease(type);
      	System.out.println("ClientINFO:MOUSE move to "+x+","+y+" AND execute TYPE IS click "+type);
      }
    	}else if("mousePressed".equals(s[0].trim())){//operateStr Model: mousePressed,x,y,type
      int type = Integer.parseInt(s[s.length - 1].trim());
      if (s.length == 4) {
      	int x = Integer.parseInt(s[1].trim());
      	int y = Integer.parseInt(s[2].trim());
      	robot.mouseMove(x, y);
      	robot.mousePress(type);
      	System.out.println("ClientINFO:MOUSE move to "+x+","+y+" AND execute TYPE IS press "+type);
      }
    	}else if("mouseReleased".equals(s[0].trim())){//operateStr Model: mouseReleased,x,y,type
      int type = Integer.parseInt(s[s.length - 1].trim());
      if (s.length == 4) {
      	int x = Integer.parseInt(s[1].trim());
      	int y = Integer.parseInt(s[2].trim());
      	robot.mouseMove(x, y);
      	robot.mouseRelease(type);
      	System.out.println("ClientINFO:MOUSE move to "+x+","+y+" AND execute TYPE IS release  "+type);
      }
    	}else if("mouseDragged".equals(s[0].trim())){//operateStr Model: mouseDragged,x,y,type
      if (s.length == 4) {
      	int x = Integer.parseInt(s[1].trim());
      	int y = Integer.parseInt(s[2].trim());
      	robot.mouseMove(x, y);
      	System.out.println("ClientINFO:MOUSE move to "+x+","+y );
      }
    	}else if("mouseMoved".equals(s[0].trim())){
      if (s.length == 3) {
      	int x = Integer.parseInt(s[1].trim());
      	int y = Integer.parseInt(s[2].trim());
      	robot.mouseMove(x, y);
      	System.out.println("ClientINFO:MOUSE move to "+x+","+y);
      }
    	}else if("keyPress".equals(s[0].trim())){
      if(s.length==2){
      	int keycode=Integer.parseInt(s[1]);
      	robot.keyPress(keycode);
      }
    	}else if("keyRelease".equals(s[0].trim())){
      if(s.length==2){
      	int keycode=Integer.parseInt(s[1]);
      	robot.keyRelease(keycode);
      }
    	}else if("keyTyped".equals(s[0].trim())){
      if(s.length==2){
      	int keycode=Integer.parseInt(s[1]);
      	robot.keyPress(keycode);
      	robot.keyRelease(keycode);
      }
    	}
    }
  	} catch (IOException e) {
    System.out.println("error1");
  	}
  }
	}
}


import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipInputStream;

import javax.imageio.ImageIO;
import javax.swing.*;

public class Client{
	public static void main(String args[]) {

  ServerGUI sendOrder=new ServerGUI("127.0.0.1", "实时操控");//被监控电脑的ip地址
  WriteGUI catchScreen=new WriteGUI(sendOrder);
  catchScreen.changePort(30009);//现在可以修改获取主机屏幕信息要访问的端口号
  new Thread(catchScreen).start();//启动线程
	}
}

/**
 * @author LanXJ @doctime 2010-7-8
 * 访问指定端口的服务,从服务器端读取图像流,生成(刷新)管理面板
 * 默认访问的端口为DEFAULT_PORT=30011 端口,
 * 可以通过changePort来改变访问端口,也可以通过getPort查看当前访问端口
 * 实例化线程类时需要传入一个ServerGUI类型的辅助窗体对象
 */
class WriteGUI extends Thread {
	public static final int DEFAULT_PORT=30011;
	private int port;
	private ServerGUI rec;

	/**
  * @param rec 辅助窗体对象,可通过实例化获得
  */
	public WriteGUI(ServerGUI rec) {
  this.port=WriteGUI.DEFAULT_PORT;
  this.rec = rec;
	}
	public void changePort(int port){
  this.port=port;
	}
	public int getPort(){
  return this.port;
	}
	public void run() {
  while (rec.getBoo()) {
  	System.out.println((System.currentTimeMillis()/1000)%24%60);
  	Socket socket = null;
  	try {
    socket = new Socket(rec.getIP(), this.port);
    DataInputStream dis = new DataInputStream(socket.getInputStream());
    ZipInputStream zis = new ZipInputStream(dis);
    Image image = null;
    try {
    	zis.getNextEntry();// 读取下一个 ZIP 文件条目并将流定位到该条目数据的开始处
    	image = ImageIO.read(zis);// 把ZIP流转换为图片
    	rec.jlabel.setIcon(new ImageIcon(image));
    	rec.scroll.setViewportView(rec.jlabel);
    	rec.validate();
    } catch (IOException ioe) {}
    try{
//    	dis.close();
    	zis.close();
    }catch (Exception e) {}
    try {
    	TimeUnit.MILLISECONDS.sleep(50);// 接收图片间隔时间
    } catch (InterruptedException ie) {
    	ie.printStackTrace();
    }
  	} catch (IOException ioe) {
  	} finally {
    try {
    	socket.close();
    } catch (IOException e) {}
  	}
  }
	}
}

/**
 * @author LanXJ @doctime 2010-7-8
 * 访问指定主机的指定端口,向主机发送实例化线程类时传入的操控命令,实现对该主机的操控
 * 默认访问服务端口为DEFAULT_PORT=30012 端口,主机IP为实例化线程类时传入的IP
 * 可以通过changePort和changeIP来修改访问的端口和主机
 * 也可以通过setOperateStr来设置需要发送的操控命令
 * 需要注意的是,修改访问端口或主机必须在线程启动之前修改,否则修改无效
 */
class SendOperate extends Thread {
	public static int DEFAULT_PORT=30012;
	private String ip;
	private int port;// 30012
	private String operateStr;

	public SendOperate(String ip, String operateStr) {
  this.ip = ip;
  this.port = SendOperate.DEFAULT_PORT;
  this.operateStr = operateStr;
	}
	public void setOperateStr(String operateStr){
  this.operateStr=operateStr;
	}
	public void changePort(int port){
  this.port=port;
	}
	public boolean changeIP(String ip){
  if(UtilServer.checkIp(ip)){
  	this.ip=ip;
  	return true;
  }
  return false;
	}
	public int getPort(){
  return this.port;
	}
	public String getIP(){
  return this.ip;
	}
	public void run() {
  if(this.operateStr==null||this.operateStr.equals("")){
  	return;
  }
//  if(this.operateStr.trim().startsWith("mouseMoved")){
//  	return;
//  }
  try {
  	Socket socket = new Socket(this.ip, this.port);
  	OutputStream os = socket.getOutputStream();
  	os.write((this.operateStr).getBytes());
  	os.flush();
  	socket.close();
  	System.out.println("INFO: 【SendOperate】ip=" + this.ip + ",port=" + this.port + ",operateStr="" + this.operateStr + "".");
  } catch (UnknownHostException e) {
  	e.printStackTrace();
  } catch (IOException e) {
  	e.printStackTrace();
  }
	}
	
}

/**
 * @author LanXJ @doctime 2010-7-8
 * 服务工具类
 */
class UtilServer{
	public static boolean checkIp(String ip){
  if(ip==null)return false;
  String []dps=ip.split("\.");
  if(dps.length!=4&&dps.length!=6)return false;
  boolean isIp=true;
  for (int i = 0; i < dps.length; i++) {
  	try {
    int dp=Integer.parseInt(dps[i]);
    if(dp>255||dp< 0){
    	throw new RuntimeException("error IP");
    }
  	} catch (Exception e) {
    isIp=false;
    break;
  	}
  }
  return isIp;
	}
}
/**
 * @author LanXJ @doctime 2010-7-8
 * serverManage的辅助窗体,内部事件封装了sendOperate的实现
 */
class ServerGUI extends JFrame {
	private static final long serialVersionUID = 2273190419221320707L;
	JLabel jlabel;
	JScrollPane scroll;
	private String ip;
	private int port;
	private boolean boo;
	public boolean getBoo(){
  return this.boo;
	}
	public int getPort(){
  return this.port;
	}
	public void changePort(int port){
  this.port=port;
	}
	public String getIP(){
  return this.ip;
	}
	public boolean changeIP(String ip){
  if(UtilServer.checkIp(ip)){
  	this.setTitle(this.getTitle().replace(this.ip, ip));
  	this.ip=ip;
  	return true;
  }
  return false;
	}

	protected ServerGUI(String IP, String sub) {
  this.boo = true;
  this.ip = IP;
  this.port=SendOperate.DEFAULT_PORT;
  this.setTitle("远程监控--IP:" + IP + "--主题:" + sub);
  this.jlabel = new JLabel();
  this.scroll = new JScrollPane();
  this.scroll.add(this.jlabel);
  scroll.addMouseListener(new MouseAdapter() {
  	/*public void mouseClicked(MouseEvent e) {// getMousePosition()
    super.mouseClicked(e);
    //由于加上单击事件后,鼠标按下并快速抬起 就设计到按下、抬起、单击 三个事件,将单击变为了双击
    //所以不实现单击监听
    int x = (int) e.getX() + (int) ServerGUI.this.scroll.getHorizontalScrollBar().getValue();
    int y = (int) e.getY() + (int) ServerGUI.this.scroll.getVerticalScrollBar().getValue();
//    int type = e.getModifiers();//e.BUTTON1_MASK 或 e.BUTTON2_MASK 或 e.BUTTON3_MASK
    String operateStr ="mouseClicked,"+ x + "," + y + "," + e.getModifiers();
    
    SendOperate sender=new SendOperate(ServerGUI.this.ip, (operateStr));
    sender.changeIP(ServerGUI.this.ip);//同步ip
    sender.changePort(ServerGUI.this.port);//同步port
    sender.start();
  	}*/
  	
  	public void mousePressed(MouseEvent e) {
    super.mousePressed(e);
    int x = (int) e.getX() + (int) ServerGUI.this.scroll.getHorizontalScrollBar().getValue();
    int y = (int) e.getY() + (int) ServerGUI.this.scroll.getVerticalScrollBar().getValue();
//    int type = e.getModifiers();//e.BUTTON1_MASK 或 e.BUTTON2_MASK 或 e.BUTTON3_MASK
    String operateStr ="mousePressed,"+ x + "," + y + "," + e.getModifiers();
    
    SendOperate sender=new SendOperate(ServerGUI.this.ip, (operateStr));
    sender.changeIP(ServerGUI.this.ip);//同步ip
    sender.changePort(ServerGUI.this.port);//同步port
    sender.start();
  	}
  	@SuppressWarnings("static-access")
  	public void mouseReleased(MouseEvent e) {
    super.mouseReleased(e);
    int x = (int) e.getX() + (int) ServerGUI.this.scroll.getHorizontalScrollBar().getValue();
    int y = (int) e.getY() + (int) ServerGUI.this.scroll.getVerticalScrollBar().getValue();
//    int type = e.getModifiers();//e.BUTTON1_MASK 或 e.BUTTON2_MASK 或 e.BUTTON3_MASK
    String operateStr ="mouseReleased,"+ x + "," + y + "," + e.getModifiers();
    
    SendOperate sender=new SendOperate(ServerGUI.this.ip, (operateStr));
    sender.changeIP(ServerGUI.this.ip);//同步ip
    sender.changePort(ServerGUI.this.port);//同步port
    sender.start();
  	}
  });
  scroll.addMouseMotionListener(new MouseMotionAdapter(){
  	public void mouseDragged(MouseEvent e) {
    super.mouseDragged(e);
    int x = (int) e.getX() + (int) ServerGUI.this.scroll.getHorizontalScrollBar().getValue();
    int y = (int) e.getY() + (int) ServerGUI.this.scroll.getVerticalScrollBar().getValue();
    String operateStr ="mouseDragged,"+ x + "," + y + "," + e.getModifiers();
    
    SendOperate sender=new SendOperate(ServerGUI.this.ip, operateStr);
    sender.changeIP(ServerGUI.this.ip);//同步ip
    sender.changePort(ServerGUI.this.port);//同步port
    sender.start();
  	}
  	public void mouseMoved(MouseEvent e) {
    super.mouseMoved(e);
    int x = (int) e.getX() + (int) ServerGUI.this.scroll.getHorizontalScrollBar().getValue();
    int y = (int) e.getY() + (int) ServerGUI.this.scroll.getVerticalScrollBar().getValue();
    String operateStr ="mouseMoved,"+ x + "," + y;
    
    SendOperate sender=new SendOperate(ServerGUI.this.ip, (operateStr));
    sender.changeIP(ServerGUI.this.ip);//同步ip
    sender.changePort(ServerGUI.this.port);//同步port
    sender.start();
  	}
  });
  this.addKeyListener(new KeyAdapter(){
  	public void keyPressed(KeyEvent e) {
    super.keyPressed(e);
    String operateStr ="keyPress,"+ e.getKeyCode();
    
    SendOperate sender=new SendOperate(ServerGUI.this.ip, (operateStr));
    sender.changeIP(ServerGUI.this.ip);//同步ip
    sender.changePort(ServerGUI.this.port);//同步port
    sender.start();
  	}
  	public void keyReleased(KeyEvent e) {
    super.keyReleased(e);
    String operateStr ="keyReleas,"+ e.getKeyCode();
    
    SendOperate sender=new SendOperate(ServerGUI.this.ip, (operateStr));
    sender.changeIP(ServerGUI.this.ip);//同步ip
    sender.changePort(ServerGUI.this.port);//同步port
    sender.start();
  	}
  	public void keyTyped(KeyEvent e) {
//    super.keyTyped(e);
  	}
  });
  this.add(scroll);

  this.setAlwaysOnTop(false);
  Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
  this.setBounds(100, 75, (int) screenSize.getWidth() - 200, (int) screenSize.getHeight() - 150);
  // this.setResizable(false);
  this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);// 关闭窗体不做任何事
  this.addWindowListener(new WindowAdapter() {
  	public void windowClosing(WindowEvent e) {
    boo = false;
    ServerGUI.this.dispose();
    System.out.println("窗体关闭");
    System.gc();
  	}
  });
  this.setVisible(true);
  this.validate();

	}

}