package org.bruce.remote.client;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
// 关闭的时候写法一定要一致!关于解码图片时发生错误问题的解决办法:一定不能采用暴力的方式
// 关闭,必须在interrupt()以后才能dispose()掉!猛然的调用dispose()的话,极容易造成
// 单张图片数据的损坏,单张图片数据损坏的后果就是:不break;的话,收到一张一张的破图;break的话
// 关闭之后再次打开的话,一切都是那么的安静,整个画面一动都不动了...纠结,又整了我几个小时,
// 就因为点叉叉关闭的方法和点菜单选项的处理方法不一致!郁闷。。
public class Client extends JFrame implements Runnable, ActionListener {
private static final long serialVersionUID = 6537101270853996382L;
private JMenuBar jmb;
private JMenu jm, jm2;
private JMenuItem jmi1, jmi2, jmi3, jmi4, jmi5, jmi6, jmi7;
//----------------------------------------------------------
public static final String serverIPAddress = "127.0.0.1";
public static final int serverUDPPort = 9998; // 这两项用于建立于服务器端的UDP连接,发送命令封包
// 用于启动一个TCP服务等待服务端连接过来,在ImageViewer里面用到
public static final int clientTCPPort = 8888;
// 命令常量
public static final String Command_Connect = "connect";
public static final String Command_Disconnect = "disconnect";
public static final String Command_Screen = "screen";
public static final String Command_Control = "control";
public static final String Command_StopScreen = "stopScreen";
public static final String Command_StopControl = "stopControl";
private ImageViewer imageViewer = null;
// private boolean isTcpServerStarted = false;
private ServerSocket ss = null; //用于接收截图
private ServerSocket ss2 = null; // 用于发送事件对象
private Socket imageSocket = null;
private Socket eventSocket = null;
private ObjectOutputStream oos = null;
public Client() {
// ①功能模块初始化
// ②界面模块初始化
this.setLocation(100, 100);
this.setSize(200, 200);
this.setResizable(false);
// this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
try {
if(!Client.this.eventSocket.isClosed()) {
// 管 Server 端的 EventReceiver 启动了没,发一次再说!
Client.this.oos.writeObject(new String("stopReceiveEvent"));
}
} catch (IOException e1) {
e1.printStackTrace();
}
System.exit(1);
}
});
this.initialComponents(); // 初始化菜单栏中的一系列选项!
this.setVisible(true);
}
private void initialComponents() {
jmb = new JMenuBar();
jm = new JMenu("(S)tart");
jm.setMnemonic('S');
jmi6 = new JMenuItem("(O)penTCPConnection");
jmi6.setMnemonic('o');
jmi6.setActionCommand(Command_Connect);
jmi6.addActionListener(this);
jmi7 = new JMenuItem("(C)loseTCPConnection");
jmi7.setMnemonic('c');
jmi7.setActionCommand(Command_Disconnect);
jmi7.setEnabled(false);
jmi7.addActionListener(this);
jmi5 = new JMenuItem("(E)xit");
jmi5.setMnemonic('e');
jmi5.setActionCommand("exit");
jmi5.addActionListener(this);
// ---------------------------------------------
jmi1 = new JMenuItem("Remote (D)esk");
jmi1.setMnemonic('d');
jmi1.setActionCommand(Command_Screen);
jmi1.setEnabled(false);
jmi1.addActionListener(this);
jmi2 = new JMenuItem("Remote (A)sistant");
jmi2.setMnemonic('a');
jmi2.setActionCommand(Command_Control);
jmi2.setEnabled(false);
jmi2.addActionListener(this);
jmi3 = new JMenuItem("(C)ancel Desk");
jmi3.setMnemonic('c');
jmi3.setActionCommand(Command_StopScreen);
jmi3.setEnabled(false);
jmi3.addActionListener(this);
jmi4 = new JMenuItem("Cance(l) Asistent");
jmi4.setMnemonic('l');
jmi4.setActionCommand(Command_StopControl);
jmi4.setEnabled(false);
jmi4.addActionListener(this);
jm2 = new JMenu("(F)unction");
jm2.setMnemonic('f');
// ---------------------------
this.setJMenuBar(jmb);
jmb.add(jm); // 1
jm.add(jmi6);
jm.add(jmi7);
jm.add(jmi5);
jmb.add(jm2); // 2
jmb.add(jm2);
jm2.add(jmi1);
jm2.add(jmi2);
jm2.add(jmi3);
jm2.add(jmi4);
}
public static void main(String[] args) {
new Client();
}
// 由于向被控制端发送命令就是一下子的事情,完全没有必要分一个线程来进行处理
// 只有接收方(总是傻傻地在那里等着别人发东西过来),那才需要弄一个单门的线程来负责!!
public void sendCommand(String cmd) {
byte[] buffer = cmd.getBytes();
DatagramPacket dp = null;
DatagramSocket ds = null;
try {
dp = new DatagramPacket(buffer, buffer.length, new InetSocketAddress(serverIPAddress, serverUDPPort));
// 这个封包从本机的 9999 UDP 端口发出!
ds = new DatagramSocket(9999);
ds.send(dp);
ds.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void startServer() {
try {
ss = new ServerSocket(clientTCPPort);
System.out.println("TCPServer 已经监听在 TCP 8888 端口了!");
ss2 = new ServerSocket(clientTCPPort + 1);
System.out.println("TCPServer 已经监听在 TCP 8889 端口了!");
// isTcpServerStarted = true;
} catch (IOException e) {
e.printStackTrace();
}
}
// 只要接收一次就OK,没必要搞个死循环不停地接收啦
public void run() {
// while(isTcpServerStarted) {
try {
// if(imageSocket == null) { // 被这个害惨了!!
imageSocket = ss.accept();
// }
// Thread.sleep(10);
// } catch (InterruptedException e) {
// break;
} catch (IOException e) {
e.printStackTrace();
System.exit(0);
}
// }
}
class EventSocketReceiver extends Thread {
public void run() {
// while(isTcpServerStarted) {
try {
// if(Client.this.eventSocket == null) { // 被上面那个和这个害惨了!
Client.this.eventSocket = ss2.accept();
Client.this.oos = new ObjectOutputStream(Client.this.eventSocket.getOutputStream());
// }
// Thread.sleep(10);
// } catch (InterruptedException e) {
// break;
} catch (IOException e) {
e.printStackTrace();
System.exit(0);
}
// }
}
}
public void actionPerformed(ActionEvent e) {
// 1.建立与被控制端之间的 TCP 连接(只有打开 TCP 连接才可以 远程桌面)
if(e.getActionCommand().equals(Command_Connect)) {
// ①启动控制端的 ServerSocket ,为进行下一步处理做好铺垫!
this.startServer(); //忘了这句代码,真混蛋!
Thread t = new Thread(this);
t.setDaemon(true); // 很重要的一句代码,否则 ImageViewer 对象不能彻底退出!
t.start();
// ---------------------------------------------------------------
Thread t2 = new Thread(new EventSocketReceiver());
t2.setDaemon(true);
t2.start();
// ================================================================
// ②ServerSocket 已准备好,发送 UDP 封包通知 被控制端建立与控制端的 TCP 连接
this.sendCommand(Command_Connect);
// ③TCP连接打开以后就可以自由的使用 远程桌面,远程协助的 功能了
jmi6.setEnabled(false); // 让自己失效(不允许重复进行连接!!)
jmi7.setEnabled(true); // TCP 连接已经打开,让关闭 TCP 连接的选项被激活
jmi1.setEnabled(true); // 远程桌面
jmi2.setEnabled(true); // 远程协助
}
// 2.关闭 TCP 连接
if(e.getActionCommand().equals(Command_Disconnect)) {
if(this.imageViewer != null) {
this.imageViewer.removeListener(); // 第一步是断了在 eventSocket 上面执行写操作的可能(让监听器失效)!
// this.imageViewer.dispose();
}
try {
// 第二步:①为保证在 socket 连接关闭后,Server 端不会再出现在该 even