优化是一个很棘手的问题,最好的策略就是置之不顾,直到你发现需要担心它了
最近一直在复习之前所学过的知识,今天看到之前写过的一段程序,所以在这里写上一篇博客记录一下。
如果我们现在有一个处理数据的任务很耗费资源和时间我们该怎么办呢?这就是本篇博客要讲的内容,我们可以使用远程服务器来执行处理数据的任务。那该怎么做呢,你可以想以下把大象放进冰箱需要几步,3步,把冰箱打开,把大象放进去,把冰箱关上,那我们做这项操作也只需要三步,把任务和信息给服务器,服务器执行任务,返回给客户端结果。
那我们都需要用到什么呢?
1.包裹处理数据类的jar包
2.jar包的运行指令
3.socket通信
4.i/o流
那就动手开干吧!
1.编写任务并打包测试
在这里写一个简单的统计单词个数的例子作为服务器要执行的任务
单词计数总体来说分两步,一步是读数据,一步是分词计数
BufferedReader bufferedReader=new BufferedReader(new FileReader("C:\\Users\\liuyue\\IdeaProjects\\Distributed\\word.txt"));
StringBuffer stringBuffer=new StringBuffer();
String line;
while ((line=bufferedReader.readLine())!=null){
stringBuffer.append(line);
}
bufferedReader.close();
String inputString=stringBuffer.toString();
使用BufferedReader包裹FileReader读取文件到String对象
String regex="\\b[a-zA-Z]+\\b";
编写查找单词的正则表达式
Pattern pattern=Pattern.compile(regex);
Matcher matcher=pattern.matcher(inputString);
生成正则对象、生成匹配器
int count=0;
while (matcher.find()){
count++;
}
System.out.println(count);
循环查找计数
书写完你可以测试一下程序是不是可以跑的通,然后将其打成jar包,如果你不会使用IDEA打jar包你可以去读读这篇文章使用IDEA打包程序到jar包
打完包之后你需要运行一下测试是不是正确,你可以使用Runtime类来执行你的jar包,也可以从中得到程序的输出
Runtime 类代表着Java程序的运行时环境,每个Java程序都有一个Runtime实例,该类会被自动创建,我们可以通过Runtime.getRuntime()
方法来获取当前程序的Runtime实例。
Runtime runtime = Runtime.getRuntime();
使用exec()方法传入指令来执行jar包,该方法返回一个Precess对象,用于管理子进程,我们可以从中得到程序的输出
Process process = runtime.exec("java -jar C:\\Users\\liuyue\\IdeaProjects\\Distributed\\out\\artifacts\\Distributed_jar\\Distributed.jar");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "gbk"));
System.out.println(bufferedReader.readLine());
在最后不要忘记杀死子进程
process.destroy();
这样第一步就完成了
2.编写客户端
Socket socket=new Socket("localhost",8888);
首先使用Socket创建客户端套接字对象,并指定地址和端口
你可以使用netstat -nao 命令来查询电脑上的端口占用情况
BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(socket.getOutputStream());
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream()))
然后我们需要思考一下和服务器交互的流程,首先为了防止服务器被人随便请求,你可以设置一个暗号,暗号对上了才能继续。
其次我们还要至少给服务器两样东西,jar必须给,还有就是运行jar包的指令,因为有时候还需要通过指令来传参数,但我在这里还传了一个jar包保存路径,这样可以保证指令和jar包保存地址匹配,你也可以在服务器端做操作
然后我们还需要与服务端约定几句话,遇到不同情况返回不同的东西,这样方便我们处理。
对于输出给服务器的数据我们还要有一定格式,这样方便服务器读取,我们和服务器约定信息的前几个字节代表长度(不同种类信息不同),后面是真实内容
private static void writeRequestHead(BufferedOutputStream bufferedOutputStream) throws IOException {
String requestHead="gionvwyw59py8p8svg";
byte[] heads=requestHead.getBytes();
int len=heads.length;
bufferedOutputStream.write(len);
bufferedOutputStream.write(heads);
}
writeRequestHead(bufferedOutputStream);
bufferedOutputStream.flush();
String result=bufferedReader.readLine();
暗号的前四个字节代表长度,后面是真实内容。输出之后等待服务器回应
如果回应是成功,那么继续下一步操作,发送jar包路径和jar包
private static void writeJarPath(BufferedOutputStream bufferedOutputStream) throws IOException {
String path="E:/Find.jar";
byte[] bytes=path.getBytes();
int len=bytes.length;
bufferedOutputStream.write(len);
bufferedOutputStream.write(bytes);
}
writeJarPath(bufferedOutputStream);
bufferedOutputStream.flush();
private static void writeJar(BufferedOutputStream bufferedOutputStream) throws IOException {
File file=new File("C:/Users/13717/IdeaProjects/Distributed/out/artifacts/Distributed_jar/Distributed.jar");
long length=file.length();
bufferedOutputStream.write(NumberUtils.longToBytes(length));
BufferedInputStream bufferedInputStream=new BufferedInputStream(new FileInputStream(file));
int len;
byte[] bytes=new byte[1024];
while ((len=bufferedInputStream.read(bytes))!=-1){
bufferedOutputStream.write(bytes,0,len);
}
bufferedInputStream.close();
}
writeJar(bufferedOutputStream);
bufferedOutputStream.flush();
private static ByteBuffer buffer = ByteBuffer.allocate(8);
public static byte[] longToBytes(long x) {
buffer.putLong(0, x);
return buffer.array();
}
因为bufferedOutputStream无法写入long类型,所以我们需要使用ByteBuffer类来将long转换成byte数组
等待服务端响应,如果成功则发送执行jar包命令并读取服务端返回的结果
private static void writeCommand(BufferedOutputStream bufferedOutputStream) throws IOException {
String command="java -jar E:/Find.jar";
byte[] bytes=command.getBytes();
int length=bytes.length;
bufferedOutputStream.write(NumberUtils.intToByteArray(length));
bufferedOutputStream.write(bytes);
}
writeCommand(bufferedOutputStream);
bufferedOutputStream.flush();
result=bufferedReader.readLine();
System.out.println(result);
客户端编写就完成了,接下来将编写服务端
3.服务器端编写
ServerSocket serverSocket=new ServerSocket(8888);
和客户端一样,服务器端需要创建一个服务器端套接字并指定监听端口
Socket socket=serverSocket.accept();
BufferedInputStream bufferedInputStream=new BufferedInputStream(socket.getInputStream());
BufferedWriter bufferedWriter=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String result=readString(bufferedInputStream);
然后使用使用while循环接受客户端发来的请求,读取暗号,核对暗号
bufferedWriter.write("1");
bufferedWriter.newLine();
bufferedWriter.flush();
如果暗号对上了便返回一个‘’1‘’表示成功,这是我随便写的,你也可以使用别的代表,定义一个读取数据的通用方法
private static String readString(BufferedInputStream bufferedInputStream) throws IOException {
byte[] bytes=new byte[4];
bufferedInputStream.read(bytes);
int len=NumberUtils.byteArrayToInt(bytes);
bytes=new byte[len];
bufferedInputStream.read(bytes);
String result=new String(bytes);
return result;
}
因为我们约定了前四个代表大小,所以要先读前四个字节,然后创建真实内容大小的bytes数组,读取内容转换成string对象返回
String jarPath=readString(bufferedInputStream);
int flag=saveJar(bufferedInputStream,jarPath);
private static int saveJar(BufferedInputStream bufferedInputStream, String jarPath) throws IOException {
BufferedOutputStream bufferedOutputStream = null;
try {
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(jarPath));
byte[] bytes = new byte[8];
bufferedInputStream.read(bytes);
long len = NumberUtils.bytesToLong(bytes);
if (len > 1024) {
long count = len / 1024;
bytes = new byte[1024];
//先读取1024个字节
for (int i = 0; i < count; i++) {
bufferedInputStream.read(bytes);
bufferedOutputStream.write(bytes);
bufferedOutputStream.flush();
}
if (len > count * 1024) {
bytes = new byte[(int) (len - count * 1024)];
bufferedInputStream.read(bytes);
bufferedOutputStream.write(bytes);
bufferedOutputStream.flush();
}
} else {
bytes = new byte[(int) len];
bufferedInputStream.read(bytes);
bufferedOutputStream.write(bytes);
bufferedOutputStream.flush();
}
return 1;
}catch (IOException e){
e.printStackTrace();
return 2;
}finally {
if (bufferedOutputStream!=null){
bufferedOutputStream.close();
}
}
}
因为jar可能10K也可能100K,大小浮动很大,所以我们使用8个字节来保存jar包的长度,然后每次读1024个字节,循环读取,如果有剩余就读取长度除1024的余数个字节,将它保存到刚才接受的路径,如果出现错误返回错误信息,成功返回正确信息
public static long bytesToLong(byte[] bytes) {
buffer.put(bytes, 0, bytes.length);
buffer.flip();
return buffer.getLong();
}
因为byte数组无法和long类型互转,所以需要Buffer类来帮助,使用put()方法,传入要转换的byte数组,偏移量,截止位置,这样就把byte数组存到了buffer对象中,然后调用flip()方法,使buffer下标移动到起点,最后调用getLong便可以得到byte数组对应的long值了
根据saveJar方法的返回值判断保存jar包是否成功,如果成功给服务端发成功消息,告诉服务端可以发要执行的命令了
String command=readString(bufferedInputStream);
Runtime runtime=Runtime.getRuntime();
Process process=runtime.exec(command);
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(process.getInputStream(),"GBK"));
result=bufferedReader.readLine();
bufferedWriter.write(result);
bufferedWriter.newLine();
bufferedWriter.flush();
读取要执行的指令,使用Runtime执行,并从返回的process中得到结果,返回给客户端
这样一轮交互变完成了,这只是个简单的例子,如果你感兴趣的话可以在其中加入更多的东西。