事情是这样的。我写了一个mc插件,然后用集合保存的数据,正常开启与关闭是会正常读取和保存数据的。然后有个人就问我如果程序突然崩溃导致数据丢失那怎么办。诚然,这种情况不是不可能。因为mc服崩溃是在个别服务器中比较常见的事情。那这时候数据就会丢失。为了保护玩家数据,我想出了这样一个法子:
1.启动插件
2.让插件去启动本地服务器
3.本地服务器监听8189号端口
4.插件与服务器通过8189号端口进行数据交互
5.服务器保存数据
这样一套流程下来,就算插件崩了,数据也不会丢失,因为数据服务器是独立于插件之外的一个程序。插件只不过是执行了cmd命令让他启动罢了。不过尽管如此,还是有个很麻烦的问题。这个第4步,插件与服务器进行交互的问题让我很头疼。因为此话题与MC无关,所以我们只讨论Java程序与本地服务器进行交互的问题。
我是这么想的,可以利用PrintWrinter与服务器进行交互,看起来就像用telnet交互一样,为了测试插件和服务器交互成果,我写了个这么个玩意:
package me.ctimet.dataserver;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Test
{
public static void main(String[] args) throws Exception
{
Socket socket = new Socket("127.0.0.1",8189);
Scanner scanner = new Scanner(socket.getInputStream(), StandardCharsets.UTF_8);
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),StandardCharsets.UTF_8),true);
Scanner sc = new Scanner(System.in);
while (true)
{
System.out.println(scanner.nextLine());
printWriter.println(sc.nextLine());
}
}
}
我依靠这个程序与服务器进行交互。但是,在测试过程中,我发现了一个很严重的问题,就是答非所问,请看这张图:
这是刚刚服务器与交互程序交互的场景,可以很明显的发现,我在键入put 1 1 1之后键入get 1,服务器返回了Null,这是不理想状况。因为理想状况应该是返回1才对。put指令是向服务器内存入数据,其中第一个1代表locationKey,第二个1代表key,第三个则代表value。这是对应的方法
这是服务器的类文件
package me.ctimet.dataserver;
import me.ctimet.dataserver.password.MakePassword;
import me.ctimet.dataserver.thread.ThreadedEchoHandler;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
public class Main
{
public static final HashSet<Thread> threads = new HashSet<>();
public static void main(String[] args) throws IOException
{
init();
start();
}
public static void init() throws IOException
{
MakePassword.make();
}
public static void start()
{
try (ServerSocket socket = new ServerSocket(8189))
{
while (true)
{
Socket incoming = socket.accept();
Runnable run = new ThreadedEchoHandler(incoming);
Thread thread = new Thread(run);
thread.start();
threads.add(thread);
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
public static void stop()
{
System.exit(0);
}
}
package me.ctimet.dataserver.data;
import java.util.HashMap;
public class Data
{
private static final HashMap<String,String> FIX = new HashMap<>();
private static final HashMap<String,String> LOC = new HashMap<>();
public static String get(String key)
{
return FIX.get(key);
}
public static void put(String locationKey,String key,String value)
{
FIX.put(key,value);
LOC.put(locationKey,key);
}
public static void clear()
{
FIX.clear();
LOC.clear();
}
public static void remove(String locationKey)
{
String key = LOC.get(locationKey);
FIX.remove(key);
LOC.remove(locationKey);
}
public static boolean containsKey(String key)
{
return FIX.containsKey(key);
}
public static boolean containsLocationKey(String key)
{
return LOC.containsKey(key);
}
}
package me.ctimet.dataserver.thread;
import me.ctimet.dataserver.Main;
import me.ctimet.dataserver.password.MakePassword;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import static me.ctimet.dataserver.data.Data.*;
public class ThreadedEchoHandler implements Runnable
{
Socket socket;
public ThreadedEchoHandler(Socket s)
{
socket = s;
}
public void run()
{
try (InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
Scanner sc = new Scanner(in, StandardCharsets.UTF_8);
PrintWriter pr = new PrintWriter(new OutputStreamWriter(out,StandardCharsets.UTF_8),true))
{
pr.println("Type the password:");
while (sc.hasNextLine())
{
if (sc.nextLine().equals(MakePassword.pps.getProperty("pw")))
{
doing(sc, pr);
break;
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
public void doing(Scanner sc, PrintWriter pr)
{
pr.println("Connect succeed");
Say say = new Say(pr);
String command;
String argsGet;
String[] argsPut;
while (sc.hasNextLine())
{
command = sc.nextLine();
argsGet = subArgsForGet(command);
argsPut = subArgsForPut(command);
switch (subCommand(command))
{
case "get" -> say.say(get(argsGet));
case "put" -> {
put(argsPut[0],argsPut[1],argsPut[2]);
//say.say(argsPut[0]);
//say.say(argsPut[1]);
//say.say(argsPut[2]);
say.say("Type...");
}
case "stop" -> Main.stop();
case "clear" -> {
clear();
say.say("Type...");
}
case "delete" -> {
remove(argsGet);
say.say("Type...");
}
case "continue" -> {
ThreadContinue.time = 3;
say.say("Type...");
}
case "containsKey" -> say.say(String.valueOf(containsKey(argsGet)));
}
}
}
String subCommand(String com)
{
for (int i = 0; i < com.length(); i ++)
{
if (com.charAt(i) == ' ')
{
return com.substring(0,i);
}
}
return "";
}
String subArgsForGet(String com)
{
for (int i = 0; i < com.length(); i ++)
{
if (com.charAt(i) == ' ')
{
return com.substring(i+2);
}
}
return "";
}
String[] subArgsForPut(String com)
{
String lk = subCommand(subArgsForGet(com));
String k = subArgsForGet(subArgsForGet(com));
String v = subArgsForGet(subArgsForGet(subArgsForGet(com)));
return new String[]{lk,k,v};
}
static class Say
{
PrintWriter pr;
public Say(PrintWriter printWriter)
{
pr = printWriter;
}
public void say(String mes)
{
pr.println(mes);
}
}
}
package me.ctimet.dataserver.thread;
public class ThreadContinue
{
public static int time = 3;
}
package me.ctimet.dataserver.password;
import org.jetbrains.annotations.NotNull;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.Random;
public class MakePassword
{
public static final Properties pps = new Properties();
public static void make() throws IOException
{
pps.load(new FileReader("resource/pw.properties"));
String pw = getRandomPassword();
pps.setProperty("pw",pw);
pps.store(new FileWriter("resource/pw.properties"),pw);
}
@NotNull
public static String getRandomPassword()
{
Random r = new Random();
String password = "";
while (password.length() < 50)
{
password = password + r.nextInt(10);
}
return password;
}
}
非常感谢!