前几天老大给我一个任务,要求我用java做一个发送端,模拟一个双IP终端向定时间间隔向两台服务器发送登录指令,根据服务器是否有回馈来判断服务器软件是否异常关闭。
1、每隔3分钟发送一次登录,这个肯定是多线程,要做两个线程,分别向两天服务器发送登录,然后立马将UDP接口转换成接收状态,但是转换之后,如果收到数据,可以顺利执行下面代码;如果没有收到数据,会一直处于接收状态,进入阻塞,后续的关闭UDP通道的指令也不能执行。因为这个必须3分钟一次登录,我只能在每次收到(或者是没收到)数据就及时将连接socket关闭,否则下次新建UDP socket的时候会报异常,这也是这个问题的根结(UDP socket 阻塞了)。。。
2、整个程序的布置就是要将向多个IP发送登录、接收回馈、报警、写日志等一系列动作放在线程中执行,IP、端口、数据包等信息均放在配置文件中读取。
模块设计:
1、数据字节处理
public static byte[] hex2byte(String s1)//输入为16进制字符串,转换成byte字节
{
String digital = "0123456789ABCDEF";
char[] hex2char = s1.toCharArray();
byte[] bytes = new byte[s1.length() / 2];
int temp;
for (int i = 0; i < bytes.length; i++) {
temp = digital.indexOf(hex2char[2 * i]) * 16;
temp += digital.indexOf(hex2char[2 * i + 1]);
bytes[i] = (byte) (temp & 0xff);
}
return bytes;
}
2、建立UDP socket
发送与接收都用同一个端口,这对UDP通信来说是比较复杂的,原因有两点:其一、UDP通信是面向无连接的,服务端的代码只负责接收数据,监听固定端口,而客户端,在建立socket通道的时候就可以指定本地端口(也可不指定,此时客户端操作系统随机指定端口),在发送时,监控软件处于客户端;在接收时,监控软件处于服务端。如果要使程序死循环执行下去,每隔3分钟发送一个登录包,即每三分钟进行一次客户端和服务端的切换。其二、UDP的receive方法是阻塞函数(这个我要做下解释,阻塞即意味着,在阻塞发生时,程序停留在这个地方,其后面的代码不执行),当然,这个可以通过设置socket的timeout值来迫使阻塞破裂,进而执行catch (IOException e) {...}里面的代码,这个对于多进程死循环程序很重要。
3、多线程执行代码处理与共享内存管理
多线程对内存共享是一种很重要的技术,这对于处理多任务时至关重要,因为对变量的修改要加锁,所以将变量多线程执行段放在synchronized中执行,例如
synchronized (this) {
frame.appendString("\r\n <== Receive data from "+datagramPacket.getAddress().toString());
}
这样,在frame中显示的内容就直接根据当前进程显示内容滚动显示。
4、读入配置文件(文本文件与声音文件)
读配置文件是很关键的一步,一直纠结与编译前和编译后,对相对路径的迷惑,我一般采用指定绝对路径的方法,说不定哪天能够意外发现大牛的某篇博客而茅塞顿开,在就不谈路径问题。
一度面临先有鸡先有蛋的问题迷惑不已,软件执行--读配置文件--加载配置文件--执行输出。这似乎成了小程序的固定模式,当然,也可以在读配置文件之前,通过UI来给用户自定义配置文件路径,这样似乎也有点不科学,软件如果都做的这么傻瓜了,你的软件就肯定会贬值,当然也会获得很好的用户体验。
public void Alert(String alert_file_path) {//读声音文件
try {
FileInputStream fileau=new FileInputStream(alert_file_path);
AudioStream as=new AudioStream(fileau);
AudioPlayer.player.start(as);
} catch (IOException e) {
e.printStackTrace();
}
}
配置文件中要加载很多台服务器的IP地址、端口,本地端口,发送数据字节(0x),这些数据我还是推荐用数组来处理,而且,安全性更高,如果我给用户的最大值为1billion,是否可以认为,用户将拿我的软件对GPS运营商进行暴力攻击?
5、写日志文件
写日志文件对很多人来说都不值得提,因为太多的博客在教我们read & write log files,这里不再复述。
6、动态信息打印
动态信息打印的基础是你先画出一个frame,这个可以参看某些基础教材,本人比较喜欢看大学教材,虽然过于简单,但是确实看完印象深刻。
7、主函数
主函数的设计是对整个软件运行流程的把握,下面直接上代码
public static void main(String[] args){
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (InstantiationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IllegalAccessException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (UnsupportedLookAndFeelException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
frame=new DragingFrame();
frame.setSize(400, 300);
frame.setLocation(300, 300);
frame.setResizable(false);
frame.setTitle("服务端监控程序 developed by forilen V1.3");
frame.setVisible(true);
// JOptionPane.showMessageDialog(null, "程序已经打开,切勿重复打开!");
readFile rf=new readFile();
String [][]config;
config =rf.readFileByLines("C:/alter_info/alter.conf");//Read the configure text(IP,port,packages) from the file
int timeout=Integer.parseInt(config[0][0]);
int frequency=Integer.parseInt(config[0][1]);
int auto=Integer.parseInt(config[0][2]);
int total=Integer.parseInt(config[0][3])+1;
while (flag) {//根据flag配置文件,设置参数是否死循环执行
for (int i = 1; i < total; i++) {
test_so r1 = new test_so("1s");
r1.set_tag(config[i][0], config[i][1], config[i][2],timeout,config[i][3]);
Thread t1 = new Thread(r1, "t1");
t1.start();
}
try {
Thread.sleep(frequency*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (auto==0) {
continue;
}
else {
auto--;
if (auto==0) {
break;
}
}
}
}
8、程序发布
程序发布我还是建议直接发布成jar包就行了,exe文件运行都太过肤浅,装了jre的机子都支持jar包的执行