Initial commit

This commit is contained in:
2022-10-24 02:43:46 +08:00
commit 0bdc4d0cd8
59 changed files with 3199 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
package cn.hamster3.mc.plugin.ball.server;
import cn.hamster3.mc.plugin.ball.server.command.CommandHandler;
import cn.hamster3.mc.plugin.ball.server.config.ServerConfig;
import cn.hamster3.mc.plugin.ball.server.connector.BallChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
public class Bootstrap {
private static final Logger LOGGER = LoggerFactory.getLogger("Bootstrap");
public static void main(String[] args) throws IOException {
if (initDefaultFile()) {
System.out.println("请重新启动该程序.");
return;
}
ServerConfig.init();
LOGGER.info("配置文件加载完成.");
NioEventLoopGroup loopGroup = new NioEventLoopGroup(ServerConfig.getNioThread());
ServerBootstrap bootstrap = new ServerBootstrap()
.group(loopGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(BallChannelInitializer.INSTANCE);
ChannelFuture channelFuture = bootstrap.bind(ServerConfig.getHost(), ServerConfig.getPort());
channelFuture.addListener(future -> {
if (future.isSuccess()) {
LOGGER.info("服务器已启动. 输入 stop 来关闭该程序.");
} else {
LOGGER.error("仓鼠球服务器启动失败!", future.cause());
loopGroup.shutdownGracefully();
}
});
CommandHandler.INSTANCE.start(loopGroup);
}
private static boolean initDefaultFile() throws IOException {
boolean saved = false;
File log4jFile = new File("log4j2.xml");
if (!log4jFile.exists()) {
InputStream stream = ServerConfig.class.getResourceAsStream("/log4j2.xml");
if (stream == null) {
throw new IOException("log4j2.xml 文件损坏!");
}
Files.copy(stream, log4jFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
System.out.println("已生成默认 log4j2.xml 文件!");
saved = true;
}
File configFile = new File("config.yml");
if (!configFile.exists()) {
InputStream stream = ServerConfig.class.getResourceAsStream("/config.yml");
if (stream == null) {
throw new IOException("config.yml 文件损坏!");
}
Files.copy(stream, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
System.out.println("已生成默认 config.yml 文件!");
saved = true;
}
return saved;
}
}

View File

@@ -0,0 +1,67 @@
package cn.hamster3.mc.plugin.ball.server.command;
import io.netty.channel.nio.NioEventLoopGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
public class CommandHandler {
public static final CommandHandler INSTANCE = new CommandHandler();
private static final Logger LOGGER = LoggerFactory.getLogger("command");
private NioEventLoopGroup loopGroup;
private boolean started;
public void start(NioEventLoopGroup loopGroup) {
this.loopGroup = loopGroup;
started = true;
Scanner scanner = new Scanner(System.in);
LOGGER.info("命令执行器准备就绪. 输入 help 查看命令帮助.");
while (started) {
String command = scanner.nextLine();
try {
executeCommand(command);
} catch (Exception e) {
LOGGER.error("执行命令 " + command + " 时遇到了一个异常: ", e);
}
}
}
public void executeCommand(String command) throws Exception {
String[] args = command.split(" ");
switch (args[0].toLowerCase()) {
case "?":
case "help": {
help();
break;
}
case "end":
case "stop": {
stop();
break;
}
default: {
LOGGER.info("未知指令. 请输入 help 查看帮助.");
break;
}
}
}
public void help() {
LOGGER.info("===============================================================");
LOGGER.info("help - 查看帮助.");
LOGGER.info("stop - 关闭该程序.");
LOGGER.info("===============================================================");
}
public void stop() throws Exception {
started = false;
LOGGER.info("准备关闭服务器...");
loopGroup.shutdownGracefully().await();
LOGGER.info("服务器已关闭!");
}
}

View File

@@ -0,0 +1,65 @@
package cn.hamster3.mc.plugin.ball.server.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
public final class ServerConfig {
private static final Logger LOGGER = LoggerFactory.getLogger("ServerConfig");
private static String host;
private static int port;
private static int nioThread;
private static boolean enableAcceptList;
private static List<String> acceptList;
private ServerConfig() {
}
@SuppressWarnings("unchecked")
public static void init() throws IOException {
File configFile = new File("config.yml");
InputStream stream = Files.newInputStream(configFile.toPath());
Map<String, Object> map = new Yaml().load(stream);
stream.close();
host = (String) map.get("host");
port = (int) map.get("port");
nioThread = (int) map.get("nio-thread");
enableAcceptList = (boolean) map.get("enable-accept-list");
acceptList = (List<String>) map.get("accept-list");
LOGGER.info("host: {}", host);
LOGGER.info("port: {}", port);
LOGGER.info("nioThread: {}", nioThread);
LOGGER.info("enableAcceptList: {}", enableAcceptList);
LOGGER.info("acceptList: {}", acceptList);
}
public static String getHost() {
return host;
}
public static int getPort() {
return port;
}
public static int getNioThread() {
return nioThread;
}
public static boolean isEnableAcceptList() {
return enableAcceptList;
}
public static List<String> getAcceptList() {
return acceptList;
}
}

View File

@@ -0,0 +1,40 @@
package cn.hamster3.mc.plugin.ball.server.connector;
import cn.hamster3.mc.plugin.ball.common.data.ServiceMessageInfo;
import cn.hamster3.mc.plugin.ball.server.constant.ConstantObjects;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BallChannelHandler extends SimpleChannelInboundHandler<String> {
private static final Logger LOGGER = LoggerFactory.getLogger("ChannelHandler");
public BallChannelHandler() {
super(true);
}
@Override
protected void channelRead0(ChannelHandlerContext context, String message) {
try {
ServiceMessageInfo messageInfo = ConstantObjects.GSON.fromJson(message, ServiceMessageInfo.class);
LOGGER.info("从服务器 {} 上收到一条消息: \n {}", messageInfo.getSenderID(), messageInfo);
BallChannelInitializer.broadcastMessage(messageInfo);
} catch (Exception e) {
LOGGER.error(String.format("处理消息 %s 时出现错误: ", message), e);
}
}
@Override
public void channelInactive(ChannelHandlerContext context) {
context.close();
BallChannelInitializer.CHANNELS.remove(context.channel());
LOGGER.warn("与服务器 {} 的连接已断开.", context.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
LOGGER.warn("与服务器 {} 通信时出现了一个错误: ", context.channel().remoteAddress(), cause);
}
}

View File

@@ -0,0 +1,57 @@
package cn.hamster3.mc.plugin.ball.server.connector;
import cn.hamster3.mc.plugin.ball.common.data.ServiceMessageInfo;
import cn.hamster3.mc.plugin.ball.server.config.ServerConfig;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class BallChannelInitializer extends ChannelInitializer<NioSocketChannel> {
public static final BallChannelInitializer INSTANCE = new BallChannelInitializer();
public static final List<Channel> CHANNELS = new ArrayList<>();
private static final Logger LOGGER = LoggerFactory.getLogger("BallServerCentre");
private BallChannelInitializer() {
}
public static void broadcastMessage(ServiceMessageInfo messageInfo) {
String string = messageInfo.toString();
for (Channel channel : CHANNELS) {
channel.writeAndFlush(string);
}
}
@Override
protected void initChannel(@NotNull NioSocketChannel channel) {
LOGGER.info("远程地址 {} 请求建立连接...", channel.remoteAddress().toString());
String hostAddress = channel.remoteAddress().getAddress().getHostAddress();
if (ServerConfig.isEnableAcceptList() && !ServerConfig.getAcceptList().contains(hostAddress)) {
channel.disconnect();
LOGGER.warn("{} 不在白名单列表中, 已断开连接!", hostAddress);
return;
}
channel.pipeline()
.addLast(new LengthFieldPrepender(8))
.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 8, 0, 8))
.addLast(new StringDecoder(StandardCharsets.UTF_8))
.addLast(new StringEncoder(StandardCharsets.UTF_8))
.addLast(new BallChannelHandler());
CHANNELS.add(channel);
}
}

View File

@@ -0,0 +1,12 @@
package cn.hamster3.mc.plugin.ball.server.constant;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public interface ConstantObjects {
/**
* GSON 工具
*/
Gson GSON = new GsonBuilder().create();
}

View File

@@ -0,0 +1,16 @@
# 绑定网卡地址
host: "0.0.0.0"
# 绑定端口
port: 58888
# 线程池数量
# 建议设置为全服最大玩家数 / 20
# 不建议低于 5
nio-thread: 10
# 是否启用IP 白名单
enable-accept-list: true
# 允许连接至服务的 ip 名单
accept-list:
- "127.0.0.1"

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="com.mojang.util">
<Appenders>
<Console name="SysOut" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n"/>
</Console>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
<DefaultRolloverStrategy max="1000"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="SysOut" level="info"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>