This commit is contained in:
TONY_All 2023-04-16 09:31:37 +08:00
parent c534b9508b
commit 31109849fe
30 changed files with 369 additions and 144 deletions

View File

@ -109,4 +109,6 @@ api.getServer("default", players);
A: 只提供服务器并记录玩家 A: 只提供服务器并记录玩家
4. Q: 服务端是 4. Q: 服务端启动方式
A: 启动脚本,参数为端口+自定义参数

View File

@ -1,7 +1,6 @@
package cc.maxmc.msm.api; package cc.maxmc.msm.api;
import cc.maxmc.msm.api.misc.ServerInfo; import cc.maxmc.msm.api.misc.MatchInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
@ -9,12 +8,12 @@ import java.util.List;
@SuppressWarnings("unused") // API @SuppressWarnings("unused") // API
public interface MultiServerManAPI { public interface MultiServerManAPI {
@NotNull @NotNull
ServerInfo getServer(@NotNull String type, @NotNull List<String> players); MatchInfo getServer(@NotNull String type, @NotNull List<String> players);
void informEnd(int id); void informEnd(int id);
@NotNull @NotNull
ServerInfo getPlayerServer(@NotNull String player); MatchInfo getPlayerServer(@NotNull String player);
Boolean containPlayer(@NotNull String player); Boolean containPlayer(@NotNull String player);

View File

@ -0,0 +1,42 @@
package cc.maxmc.msm.api.misc;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class MatchInfo {
private final int id;
@NotNull
private final ServerInfo info;
public MatchInfo() {
id = -1;
info = new ServerInfo();
}
public MatchInfo(int id, @NotNull ServerInfo info) {
this.id = id;
this.info = info;
}
public int getId() {
return id;
}
public ServerInfo getServer() {
return info;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MatchInfo matchInfo = (MatchInfo) o;
return id == matchInfo.id && Objects.equals(info, matchInfo.info);
}
@Override
public int hashCode() {
return Objects.hash(id, info);
}
}

View File

@ -1,15 +1,34 @@
package cc.maxmc.msm.api.misc; package cc.maxmc.msm.api.misc;
import com.google.common.net.HostAndPort; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.UUID;
@SuppressWarnings("unused") // API @SuppressWarnings("unused") // API
public class ServerInfo { public class ServerInfo {
@NotNull
private final UUID uid;
@Nullable @Nullable
private final HostAndPort server; private final String server;
private final int id;
private boolean available; private boolean available;
public ServerInfo() {
uid = UUID.fromString("00000000-0000-0000-0000-000000000000");
server = null;
}
public ServerInfo(@NotNull UUID uid, @Nullable String server) {
this.uid = uid;
this.server = server;
}
@NotNull
public UUID getUid() {
return uid;
}
public boolean isAvailable() { public boolean isAvailable() {
return available; return available;
} }
@ -18,22 +37,21 @@ public class ServerInfo {
this.available = available; this.available = available;
} }
public ServerInfo() {
server = HostAndPort.fromString("127.0.0.1");
id = -1;
}
public ServerInfo(@Nullable HostAndPort server, int id) {
this.server = server;
this.id = id;
}
@Nullable @Nullable
public HostAndPort getServer() { public String getServer() {
return server; return server;
} }
public int getId() { @Override
return id; public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerInfo that = (ServerInfo) o;
return Objects.equals(server, that.server) && Objects.equals(uid, that.uid);
}
@Override
public int hashCode() {
return Objects.hash(server, uid);
} }
} }

View File

@ -1,10 +0,0 @@
import cc.maxmc.msm.child.misc.SubServer
import kotlin.io.path.Path
suspend fun main() {
val server = SubServer(10086, "test", 25571, Path("/Users/tony_all/Servers/MSM/"))
server.initServer()
server.startServer()
Thread.sleep(10000)
server.cleanup()
}

View File

@ -2,9 +2,10 @@ package cc.maxmc.msm.child
import cc.maxmc.msm.api.MultiServerManAPIProvider import cc.maxmc.msm.api.MultiServerManAPIProvider
import cc.maxmc.msm.child.api.APIImpl import cc.maxmc.msm.child.api.APIImpl
import cc.maxmc.msm.child.api.APIPacketListener import cc.maxmc.msm.child.listener.APIPacketListener
import cc.maxmc.msm.child.command.Api import cc.maxmc.msm.child.command.Api
import cc.maxmc.msm.child.command.Send import cc.maxmc.msm.child.command.Send
import cc.maxmc.msm.child.listener.ProtocolListener
import cc.maxmc.msm.child.netty.NetClient import cc.maxmc.msm.child.netty.NetClient
import cc.maxmc.msm.child.settings.Settings import cc.maxmc.msm.child.settings.Settings
import cc.maxmc.msm.common.network.netty.NetworkRegistry import cc.maxmc.msm.common.network.netty.NetworkRegistry
@ -21,8 +22,9 @@ class MultiServerMan : Plugin() {
ProxyServer.getInstance().pluginManager.registerCommand(this, Send) ProxyServer.getInstance().pluginManager.registerCommand(this, Send)
ProxyServer.getInstance().pluginManager.registerCommand(this, Api) ProxyServer.getInstance().pluginManager.registerCommand(this, Api)
ProxyServer.getInstance().pluginManager.registerListener(this, APIPacketListener) ProxyServer.getInstance().pluginManager.registerListener(this, APIPacketListener)
ProxyServer.getInstance().pluginManager.registerListener(this, ProtocolListener)
NetworkRegistry NetworkRegistry
NetClient.start(Settings.Parent.address, Settings.Parent.port) NetClient.init(Settings.Parent.address, Settings.Parent.port)
} }
override fun onDisable() { override fun onDisable() {

View File

@ -1,7 +1,7 @@
package cc.maxmc.msm.child.api package cc.maxmc.msm.child.api
import cc.maxmc.msm.api.MultiServerManAPI import cc.maxmc.msm.api.MultiServerManAPI
import cc.maxmc.msm.api.misc.ServerInfo import cc.maxmc.msm.api.misc.MatchInfo
import cc.maxmc.msm.child.netty.NetClient import cc.maxmc.msm.child.netty.NetClient
import cc.maxmc.msm.common.network.packet.PPacketAPICall import cc.maxmc.msm.common.network.packet.PPacketAPICall
import java.util.* import java.util.*
@ -13,10 +13,10 @@ object APIImpl : MultiServerManAPI {
val apiCallCache = HashMap<UUID, CompletableFuture<in Any>>() val apiCallCache = HashMap<UUID, CompletableFuture<in Any>>()
private const val TIMEOUT = 3L private const val TIMEOUT = 3L
override fun getServer(type: String, players: MutableList<String>): ServerInfo { override fun getServer(type: String, players: MutableList<String>): MatchInfo {
val packet = PPacketAPICall.PPacketCallGetServer(type, players) val packet = PPacketAPICall.PPacketCallGetServer(type, players)
NetClient.sendPacket(packet) NetClient.sendPacket(packet)
val future = CompletableFuture<ServerInfo>() val future = CompletableFuture<MatchInfo>()
apiCallCache[packet.uid] = future as CompletableFuture<Any> apiCallCache[packet.uid] = future as CompletableFuture<Any>
return future.get(TIMEOUT, TimeUnit.SECONDS) return future.get(TIMEOUT, TimeUnit.SECONDS)
} }
@ -30,10 +30,10 @@ object APIImpl : MultiServerManAPI {
return return
} }
override fun getPlayerServer(player: String): ServerInfo { override fun getPlayerServer(player: String): MatchInfo {
val packet = PPacketAPICall.PPacketCallGetPlayerServer(player) val packet = PPacketAPICall.PPacketCallGetPlayerServer(player)
NetClient.sendPacket(packet) NetClient.sendPacket(packet)
val future = CompletableFuture<ServerInfo>() val future = CompletableFuture<MatchInfo>()
apiCallCache[packet.uid] = future as CompletableFuture<Any> apiCallCache[packet.uid] = future as CompletableFuture<Any>
return future.get(TIMEOUT, TimeUnit.SECONDS) return future.get(TIMEOUT, TimeUnit.SECONDS)
} }

View File

@ -1,8 +1,8 @@
package cc.maxmc.msm.child.api package cc.maxmc.msm.child.listener
import cc.maxmc.msm.child.api.APIImpl
import cc.maxmc.msm.common.event.PacketReceiveEvent import cc.maxmc.msm.common.event.PacketReceiveEvent
import cc.maxmc.msm.common.network.packet.CPacketAPICallback import cc.maxmc.msm.common.network.packet.CPacketAPICallback
import cc.maxmc.msm.common.utils.debug
import net.md_5.bungee.api.plugin.Listener import net.md_5.bungee.api.plugin.Listener
import net.md_5.bungee.event.EventHandler import net.md_5.bungee.event.EventHandler
@ -21,12 +21,12 @@ object APIPacketListener : Listener {
} }
is CPacketAPICallback.CPacketCallbackGetPlayerServer -> { is CPacketAPICallback.CPacketCallbackGetPlayerServer -> {
future?.complete(packet.serverInfo) future?.complete(packet.matchInfo)
?: throw IllegalStateException("Packet callback received, however no request") ?: throw IllegalStateException("Packet callback received, however no request")
} }
is CPacketAPICallback.CPacketCallbackGetServer -> { is CPacketAPICallback.CPacketCallbackGetServer -> {
future?.complete(packet.serverInfo) future?.complete(packet.matchInfo)
?: throw IllegalStateException("Packet callback received, however no request") ?: throw IllegalStateException("Packet callback received, however no request")
} }

View File

@ -0,0 +1,53 @@
package cc.maxmc.msm.child.listener
import cc.maxmc.msm.child.MultiServerMan
import cc.maxmc.msm.child.misc.SubServer
import cc.maxmc.msm.child.netty.NetClient
import cc.maxmc.msm.child.settings.Settings
import cc.maxmc.msm.common.event.ChannelInactiveEvent
import cc.maxmc.msm.common.event.PacketReceiveEvent
import cc.maxmc.msm.common.network.packet.CPacketGetInfo
import cc.maxmc.msm.common.network.packet.CPacketRequestServer
import cc.maxmc.msm.common.network.packet.PPacketChildInfo
import cc.maxmc.msm.common.network.packet.PPacketServerStarted
import cc.maxmc.msm.common.utils.log
import cc.maxmc.msm.common.utils.pluginScope
import kotlinx.coroutines.launch
import net.md_5.bungee.api.plugin.Listener
import net.md_5.bungee.event.EventHandler
import java.util.*
import kotlin.io.path.isDirectory
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.name
object ProtocolListener : Listener {
val serverCache = HashMap<UUID, SubServer>()
@EventHandler
fun onGetInfo(packetE: PacketReceiveEvent) {
if (packetE.packet !is CPacketGetInfo) return
val types = MultiServerMan.instance.dataFolder.toPath().resolve("patterns").listDirectoryEntries()
.filter { it.isDirectory() }.map { it.name }.toMutableSet()
val packetCallBack = PPacketChildInfo(Settings.portRange.toHashSet(), types)
NetClient.sendPacket(packetCallBack)
}
@EventHandler
fun onRequestServer(packetE: PacketReceiveEvent) {
val packet = packetE.packet as? CPacketRequestServer ?: return
val serverInfo = packet.serverInfo
val subServer = SubServer(serverInfo.uid, packet.type, serverInfo.server!!.split(":")[1].toInt())
pluginScope.launch {
subServer.initServer()
subServer.startServer()
NetClient.sendPacket(PPacketServerStarted(serverInfo))
}
}
fun onDisconnect(packet: ChannelInactiveEvent) {
repeat(10) {
log("Remote Disconnected.")
}
}
}

View File

@ -5,18 +5,19 @@ import cc.maxmc.msm.child.utils.ScriptRunner
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.nio.file.Path import java.nio.file.Path
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.io.path.copyToRecursively import kotlin.io.path.copyToRecursively
import kotlin.io.path.createDirectories import kotlin.io.path.createDirectories
import kotlin.io.path.deleteRecursively import kotlin.io.path.deleteRecursively
@OptIn(kotlin.io.path.ExperimentalPathApi::class) @OptIn(kotlin.io.path.ExperimentalPathApi::class)
class SubServer( class SubServer(
val id: Int, val uid: UUID, val type: String, val port: Int, baseFolder: Path = MultiServerMan.instance.dataFolder.toPath()
val type: String,
val port: Int,
baseFolder: Path = MultiServerMan.instance.dataFolder.toPath()
) { ) {
private val serverFolder = baseFolder.resolve("cache").resolve(id.toString()).also { it.createDirectories() } private val serverFolder =
baseFolder.resolve("cache").resolve(uid.toString().substring(0..7)).also { it.createDirectories() }
private val patternFolder = baseFolder.resolve("pattern").resolve(type).also { it.createDirectories() } private val patternFolder = baseFolder.resolve("pattern").resolve(type).also { it.createDirectories() }
private val runner = ScriptRunner(serverFolder.resolve("start.sh")) private val runner = ScriptRunner(serverFolder.resolve("start.sh"))
@ -24,16 +25,15 @@ class SubServer(
patternFolder.copyToRecursively(serverFolder, followLinks = false, overwrite = true) patternFolder.copyToRecursively(serverFolder, followLinks = false, overwrite = true)
} }
fun startServer() { suspend fun startServer() = suspendCoroutine {
runner.launch(port.toString()) runner.launch(port.toString())
runner.onOutput("For help, type \"help\" or \"?\"") { runner.onOutput("For help, type \"help\" or \"?\"") {
println("Started.") it.resume(Unit)
} }
} }
suspend fun cleanup() { suspend fun cleanup() {
runner.exec("stop") runner.exec("stop")
println("Exec stop")
runner.exit() runner.exit()
serverFolder.deleteRecursively() serverFolder.deleteRecursively()
} }

View File

@ -2,34 +2,45 @@ package cc.maxmc.msm.child.netty
import cc.maxmc.msm.common.network.BungeePacket import cc.maxmc.msm.common.network.BungeePacket
import cc.maxmc.msm.common.network.netty.NetworkRegistry import cc.maxmc.msm.common.network.netty.NetworkRegistry
import cc.maxmc.msm.common.utils.log
import cc.maxmc.msm.common.utils.pipelineInit import cc.maxmc.msm.common.utils.pipelineInit
import cc.maxmc.msm.common.utils.pluginScope
import io.netty.bootstrap.Bootstrap import io.netty.bootstrap.Bootstrap
import io.netty.channel.Channel import io.netty.channel.Channel
import io.netty.channel.ChannelFutureListener import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelOption import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioSocketChannel import io.netty.channel.socket.nio.NioSocketChannel
import kotlinx.coroutines.*
object NetClient { object NetClient {
private val loop = NioEventLoopGroup() private val loop = NioEventLoopGroup()
private lateinit var channel: Channel private lateinit var channel: Channel
private lateinit var boot: Bootstrap
fun start(address: String, port: Int) { fun init(address: String, port: Int) {
val future = Bootstrap() boot = Bootstrap().channel(NioSocketChannel::class.java).group(loop)
.channel(NioSocketChannel::class.java) .handler(pipelineInit(NetworkRegistry.PacketDirection.CHILD_BOUND)).option(ChannelOption.TCP_NODELAY, true)
.group(loop)
.handler(pipelineInit(NetworkRegistry.PacketDirection.CHILD_BOUND))
.option(ChannelOption.TCP_NODELAY, true)
.remoteAddress(address, port) .remoteAddress(address, port)
.connect().addListener(ChannelFutureListener { runBlocking {
val result = it.cause() ?: return@ChannelFutureListener println( connect(0)
"§a| §7成功连接到集群的主节点. (${ }
it.channel().remoteAddress() }
})"
) private suspend fun connect(delay: Long = 3000) {
result.printStackTrace() delay(delay)
}) log("§b| §7正在尝试连接主BC")
channel = future.channel() val future = boot.connect().await()
val result = future.cause()
if (result == null) {
channel = future.channel()
log("§a| §7成功连接到集群的主节点. (${future.channel().remoteAddress()})")
return
}
log("§c| §7未能连接到主BC将在 §c3s §7后重新连接。(原因: ${result.message})")
withContext(Dispatchers.IO) {
connect()
}
} }
fun sendPacket(packet: BungeePacket) { fun sendPacket(packet: BungeePacket) {

View File

@ -1,7 +1,14 @@
# 主BC的地址及端口
parent: parent:
address: 127.0.0.1 address: 127.0.0.1
port: 23333 port: 23333
# 该子BC所在机器所能开放的端口
ports: ports:
- 30000 - 30000
- 30001..30019 - 30001..30019
- 30443
# 该子BC所支持的服务端模版类型及模版类型启动所需参数
patterns:
main:
args: [ "-ListenPort=${port}", "-usr=usr", "-pwd=pwd" ]

View File

@ -1,11 +1,7 @@
package cc.maxmc.msm.common.network.netty package cc.maxmc.msm.common.network.netty
import cc.maxmc.msm.common.network.BungeePacket import cc.maxmc.msm.common.network.BungeePacket
import cc.maxmc.msm.common.network.packet.CPacketAPICallback import cc.maxmc.msm.common.network.packet.*
import cc.maxmc.msm.common.network.packet.CPacketDebug
import cc.maxmc.msm.common.network.packet.PPacketAPICall
import cc.maxmc.msm.common.network.packet.PPacketDebug
import cc.maxmc.msm.common.utils.debug
import com.google.common.collect.HashBiMap import com.google.common.collect.HashBiMap
object NetworkRegistry { object NetworkRegistry {
@ -18,11 +14,16 @@ object NetworkRegistry {
registerPacket(PacketDirection.PARENT_BOUND, PPacketAPICall.PPacketCallContainPlayer::class.java) registerPacket(PacketDirection.PARENT_BOUND, PPacketAPICall.PPacketCallContainPlayer::class.java)
registerPacket(PacketDirection.PARENT_BOUND, PPacketAPICall.PPacketCallInformEnd::class.java) registerPacket(PacketDirection.PARENT_BOUND, PPacketAPICall.PPacketCallInformEnd::class.java)
registerPacket(PacketDirection.PARENT_BOUND, PPacketAPICall.PPacketCallGetServer::class.java) registerPacket(PacketDirection.PARENT_BOUND, PPacketAPICall.PPacketCallGetServer::class.java)
registerPacket(PacketDirection.PARENT_BOUND, PPacketServerStarted::class.java)
registerPacket(PacketDirection.PARENT_BOUND, PPacketChildInfo::class.java)
registerPacket(PacketDirection.CHILD_BOUND, CPacketDebug::class.java) registerPacket(PacketDirection.CHILD_BOUND, CPacketDebug::class.java)
registerPacket(PacketDirection.CHILD_BOUND, CPacketAPICallback.CPacketCallbackGetPlayerServer::class.java) registerPacket(PacketDirection.CHILD_BOUND, CPacketAPICallback.CPacketCallbackGetPlayerServer::class.java)
registerPacket(PacketDirection.CHILD_BOUND, CPacketAPICallback.CPacketCallbackContainPlayer::class.java) registerPacket(PacketDirection.CHILD_BOUND, CPacketAPICallback.CPacketCallbackContainPlayer::class.java)
registerPacket(PacketDirection.CHILD_BOUND, CPacketAPICallback.CPacketCallbackInformEnd::class.java) registerPacket(PacketDirection.CHILD_BOUND, CPacketAPICallback.CPacketCallbackInformEnd::class.java)
registerPacket(PacketDirection.CHILD_BOUND, CPacketAPICallback.CPacketCallbackGetServer::class.java) registerPacket(PacketDirection.CHILD_BOUND, CPacketAPICallback.CPacketCallbackGetServer::class.java)
registerPacket(PacketDirection.CHILD_BOUND, CPacketRequestServer::class.java)
registerPacket(PacketDirection.CHILD_BOUND, CPacketGetInfo::class.java)
} }
private fun registerPacket(direction: PacketDirection, packet: Class<out BungeePacket>) { private fun registerPacket(direction: PacketDirection, packet: Class<out BungeePacket>) {

View File

@ -1,9 +1,9 @@
package cc.maxmc.msm.common.network.packet package cc.maxmc.msm.common.network.packet
import cc.maxmc.msm.api.misc.ServerInfo import cc.maxmc.msm.api.misc.MatchInfo
import cc.maxmc.msm.common.network.BungeePacket import cc.maxmc.msm.common.network.BungeePacket
import cc.maxmc.msm.common.utils.readServerInfo import cc.maxmc.msm.common.utils.readMatchInfo
import cc.maxmc.msm.common.utils.writeServerInfo import cc.maxmc.msm.common.utils.writeMatchInfo
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import net.md_5.bungee.protocol.DefinedPacket import net.md_5.bungee.protocol.DefinedPacket
import java.util.* import java.util.*
@ -20,17 +20,16 @@ sealed class CPacketAPICallback(
} }
class CPacketCallbackGetServer( class CPacketCallbackGetServer(
var serverInfo: ServerInfo = ServerInfo(), var matchInfo: MatchInfo = MatchInfo(), uid: UUID = UUID.randomUUID()
uid: UUID = UUID.randomUUID()
) : CPacketAPICallback(uid = uid) { ) : CPacketAPICallback(uid = uid) {
override fun encode(buf: ByteBuf) { override fun encode(buf: ByteBuf) {
super.encode(buf) super.encode(buf)
buf.writeServerInfo(serverInfo) buf.writeMatchInfo(matchInfo)
} }
override fun decode(buf: ByteBuf) { override fun decode(buf: ByteBuf) {
super.decode(buf) super.decode(buf)
serverInfo = buf.readServerInfo() matchInfo = buf.readMatchInfo()
} }
} }
@ -39,23 +38,21 @@ sealed class CPacketAPICallback(
) : CPacketAPICallback(uid = uid) ) : CPacketAPICallback(uid = uid)
class CPacketCallbackGetPlayerServer( class CPacketCallbackGetPlayerServer(
var serverInfo: ServerInfo, var matchInfo: MatchInfo, uid: UUID = UUID.randomUUID()
uid: UUID = UUID.randomUUID()
) : CPacketAPICallback(uid = uid) { ) : CPacketAPICallback(uid = uid) {
override fun encode(buf: ByteBuf) { override fun encode(buf: ByteBuf) {
super.encode(buf) super.encode(buf)
buf.writeServerInfo(serverInfo) buf.writeMatchInfo(matchInfo)
} }
override fun decode(buf: ByteBuf) { override fun decode(buf: ByteBuf) {
super.decode(buf) super.decode(buf)
serverInfo = buf.readServerInfo() matchInfo = buf.readMatchInfo()
} }
} }
class CPacketCallbackContainPlayer( class CPacketCallbackContainPlayer(
var value: Boolean, var value: Boolean, uid: UUID = UUID.randomUUID()
uid: UUID = UUID.randomUUID()
) : CPacketAPICallback(uid = uid) { ) : CPacketAPICallback(uid = uid) {
override fun encode(buf: ByteBuf) { override fun encode(buf: ByteBuf) {
super.encode(buf) super.encode(buf)

View File

@ -1,21 +1,23 @@
package cc.maxmc.msm.common.network.packet package cc.maxmc.msm.common.network.packet
import cc.maxmc.msm.api.misc.ServerInfo
import cc.maxmc.msm.common.network.BungeePacket import cc.maxmc.msm.common.network.BungeePacket
import cc.maxmc.msm.common.utils.readServerInfo
import cc.maxmc.msm.common.utils.writeServerInfo
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import net.md_5.bungee.protocol.DefinedPacket import net.md_5.bungee.protocol.DefinedPacket
import java.util.*
class CPacketRequestServer( class CPacketRequestServer(
var type: String, var type: String = "",
var uid: UUID = UUID.randomUUID() var serverInfo: ServerInfo = ServerInfo(),
) : BungeePacket() { ) : BungeePacket() {
override fun encode(buf: ByteBuf) { override fun encode(buf: ByteBuf) {
DefinedPacket.writeUUID(uid, buf)
DefinedPacket.writeString(type, buf) DefinedPacket.writeString(type, buf)
buf.writeServerInfo(serverInfo)
} }
override fun decode(buf: ByteBuf) { override fun decode(buf: ByteBuf) {
uid = DefinedPacket.readUUID(buf)
type = DefinedPacket.readString(buf) type = DefinedPacket.readString(buf)
serverInfo = buf.readServerInfo()
} }
} }

View File

@ -4,7 +4,8 @@ import cc.maxmc.msm.common.network.BungeePacket
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import net.md_5.bungee.protocol.DefinedPacket import net.md_5.bungee.protocol.DefinedPacket
class PPacketChildInfo(var portRange: MutableSet<Int> = HashSet(), var types: MutableSet<String>) : BungeePacket() { class PPacketChildInfo(var portRange: MutableSet<Int> = HashSet(), var types: MutableSet<String> = HashSet()) :
BungeePacket() {
override fun encode(buf: ByteBuf) { override fun encode(buf: ByteBuf) {
DefinedPacket.writeVarInt(portRange.size, buf) DefinedPacket.writeVarInt(portRange.size, buf)
portRange.forEach { portRange.forEach {

View File

@ -0,0 +1,19 @@
package cc.maxmc.msm.common.network.packet
import cc.maxmc.msm.api.misc.ServerInfo
import cc.maxmc.msm.common.network.BungeePacket
import cc.maxmc.msm.common.utils.readServerInfo
import cc.maxmc.msm.common.utils.writeServerInfo
import io.netty.buffer.ByteBuf
class PPacketServerStarted(
var serverInfo: ServerInfo = ServerInfo()
) : BungeePacket() {
override fun encode(buf: ByteBuf) {
buf.writeServerInfo(serverInfo)
}
override fun decode(buf: ByteBuf) {
serverInfo = buf.readServerInfo()
}
}

View File

@ -1,10 +1,10 @@
package cc.maxmc.msm.common.utils package cc.maxmc.msm.common.utils
import cc.maxmc.msm.api.misc.MatchInfo
import cc.maxmc.msm.api.misc.ServerInfo import cc.maxmc.msm.api.misc.ServerInfo
import cc.maxmc.msm.common.network.ClusterPacketHandler import cc.maxmc.msm.common.network.ClusterPacketHandler
import cc.maxmc.msm.common.network.netty.ClusterMsgCodec import cc.maxmc.msm.common.network.netty.ClusterMsgCodec
import cc.maxmc.msm.common.network.netty.NetworkRegistry import cc.maxmc.msm.common.network.netty.NetworkRegistry
import com.google.common.net.HostAndPort
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import io.netty.channel.Channel import io.netty.channel.Channel
import io.netty.channel.ChannelInitializer import io.netty.channel.ChannelInitializer
@ -27,15 +27,25 @@ fun pipelineInit(direction: NetworkRegistry.PacketDirection) = channelInit<Socke
} }
fun ByteBuf.writeServerInfo(info: ServerInfo) { fun ByteBuf.writeServerInfo(info: ServerInfo) {
DefinedPacket.writeUUID(info.uid, this)
DefinedPacket.writeString(info.server.toString(), this) DefinedPacket.writeString(info.server.toString(), this)
DefinedPacket.writeVarInt(info.id, this)
} }
fun ByteBuf.readServerInfo(): ServerInfo { fun ByteBuf.readServerInfo(): ServerInfo {
val uid = DefinedPacket.readUUID(this)
val hap = DefinedPacket.readString(this) val hap = DefinedPacket.readString(this)
val id = DefinedPacket.readVarInt(this)
return ServerInfo( return ServerInfo(
HostAndPort.fromString(hap), uid, hap
id
) )
}
fun ByteBuf.writeMatchInfo(match: MatchInfo) {
DefinedPacket.writeVarInt(match.id, this)
writeServerInfo(match.server)
}
fun ByteBuf.readMatchInfo(): MatchInfo {
val id = DefinedPacket.readVarInt(this)
val server = readServerInfo()
return MatchInfo(id, server)
} }

View File

@ -3,6 +3,8 @@ package cc.maxmc.msm.parent
import cc.maxmc.msm.api.MultiServerManAPIProvider import cc.maxmc.msm.api.MultiServerManAPIProvider
import cc.maxmc.msm.parent.api.APIImpl import cc.maxmc.msm.parent.api.APIImpl
import cc.maxmc.msm.parent.listener.PacketListener import cc.maxmc.msm.parent.listener.PacketListener
import cc.maxmc.msm.parent.manager.MatchManager
import cc.maxmc.msm.parent.manager.ServerManager
import cc.maxmc.msm.parent.netty.NetManager import cc.maxmc.msm.parent.netty.NetManager
import net.md_5.bungee.api.ProxyServer import net.md_5.bungee.api.ProxyServer
import net.md_5.bungee.api.plugin.Plugin import net.md_5.bungee.api.plugin.Plugin
@ -14,6 +16,8 @@ class MultiServerMan : Plugin() {
MultiServerManAPIProvider.register(APIImpl) MultiServerManAPIProvider.register(APIImpl)
ProxyServer.getInstance().pluginManager.registerListener(this, PacketListener) ProxyServer.getInstance().pluginManager.registerListener(this, PacketListener)
NetManager.startServer() NetManager.startServer()
ServerManager
MatchManager
} }
override fun onDisable() { override fun onDisable() {

View File

@ -1,9 +0,0 @@
package cc.maxmc.msm.parent
import sun.misc.Signal
import java.lang.management.ManagementFactory
import kotlin.system.exitProcess
fun main() {
}

View File

@ -1,27 +1,24 @@
package cc.maxmc.msm.parent.api package cc.maxmc.msm.parent.api
import cc.maxmc.msm.api.MultiServerManAPI import cc.maxmc.msm.api.MultiServerManAPI
import cc.maxmc.msm.api.misc.ServerInfo import cc.maxmc.msm.api.misc.MatchInfo
import com.google.common.net.HostAndPort import cc.maxmc.msm.parent.database.SQLDatabase
import cc.maxmc.msm.parent.manager.MatchManager
object APIImpl : MultiServerManAPI { object APIImpl : MultiServerManAPI {
override fun getServer(type: String, players: MutableList<String>): ServerInfo { override fun getServer(type: String, players: List<String>): MatchInfo {
TODO("not implemented") return MatchManager.requestMatch(type, players)
return ServerInfo(HostAndPort.fromString("127.0.0.1:23456"), 1024)
} }
override fun informEnd(id: Int) { override fun informEnd(id: Int) {
TODO("not implemented") MatchManager.endMatch(id)
return
} }
override fun getPlayerServer(player: String): ServerInfo { override fun getPlayerServer(player: String): MatchInfo {
TODO("not implemented") return SQLDatabase.getPlayerMatch(player)
return ServerInfo(HostAndPort.fromString("127.0.0.1:34567"), 1025)
} }
override fun containPlayer(player: String): Boolean { override fun containPlayer(player: String): Boolean {
TODO("not implemented") return getPlayerServer(player).id != -1
return true
} }
} }

View File

@ -1,11 +1,13 @@
package cc.maxmc.msm.parent.database package cc.maxmc.msm.parent.database
import cc.maxmc.msm.api.misc.MatchInfo
import cc.maxmc.msm.parent.manager.MatchManager
import cc.maxmc.msm.parent.settings.Settings import cc.maxmc.msm.parent.settings.Settings
import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.pool.HikariPool import com.zaxxer.hikari.pool.HikariPool
import java.sql.Timestamp import java.sql.Timestamp
class SQLDatabase { object SQLDatabase {
private val config = HikariConfig() private val config = HikariConfig()
private lateinit var pool: HikariPool private lateinit var pool: HikariPool
fun initDatabase() { fun initDatabase() {
@ -36,21 +38,22 @@ class SQLDatabase {
} }
} }
fun getPlayerMatch(player: String): Int { fun getPlayerMatch(player: String): MatchInfo {
pool.connection.use { pool.connection.use {
it.prepareStatement("select `id` from `match` where find_in_set(?, players) AND end IS NULL") it.prepareStatement("select `id` from `match` where find_in_set(?, players) AND end IS NULL")
.use { prepared -> .use { prepared ->
prepared.setString(0, player) prepared.setString(0, player)
val rs = prepared.executeQuery() val rs = prepared.executeQuery()
if (!rs.next()) { if (!rs.next()) {
return -1 return MatchInfo()
} }
return rs.getInt(1) val id = rs.getInt(1)
return MatchManager.getMatchById(id) ?: throw IllegalStateException("Match Not Exist in db")
} }
} }
} }
fun recordMatch(type: String, players: List<String>, start: Long = System.currentTimeMillis()) = fun recordMatch(type: String, players: List<String>, start: Long = System.currentTimeMillis()): Int =
pool.connection.use { pool.connection.use {
val prepared = it.prepareStatement( val prepared = it.prepareStatement(
""" """

View File

@ -53,10 +53,12 @@ object PacketListener : Listener {
@EventHandler @EventHandler
fun onChannelActive(evt: ChannelActiveEvent) { fun onChannelActive(evt: ChannelActiveEvent) {
log("§a| §7子BC ${evt.channel.remoteAddress()} 成功连接.")
ChildManager.registerChild(evt.channel) ChildManager.registerChild(evt.channel)
} }
fun onChannelInactive(evt: ChannelInactiveEvent) { fun onChannelInactive(evt: ChannelInactiveEvent) {
log("§c| §7子BC ${evt.channel.remoteAddress()} 断开连接.")
ChildManager.unregisterChild(evt.channel) ChildManager.unregisterChild(evt.channel)
} }
} }

View File

@ -20,10 +20,8 @@ object ChildManager {
val packet = awaitPacket(PPacketChildInfo::class.java) { ch, _ -> val packet = awaitPacket(PPacketChildInfo::class.java) { ch, _ ->
ch == channel ch == channel
} }
val child = ChildBungee(channel, packet.portRange) val child = ChildBungee(channel, packet.portRange, packet.types)
children.add(child) children.add(child)
} }
} }
@ -31,8 +29,8 @@ object ChildManager {
children.removeIf { it.channel == channel } children.removeIf { it.channel == channel }
} }
fun requestChild(): ChildBungee { fun requestChild(type: String): ChildBungee {
return children.filter { it.getAvailablePorts().isNotEmpty() }.maxByOrNull { it.getAvailablePorts().size } return children.filter { it.getAvailablePorts().isNotEmpty() && it.types.contains(type) }
?: throw IllegalStateException("当前无可用端口开启新服务器.") .maxByOrNull { it.getAvailablePorts().size } ?: throw IllegalStateException("当前无可用端口开启新服务器.")
} }
} }

View File

@ -0,0 +1,27 @@
package cc.maxmc.msm.parent.manager
import cc.maxmc.msm.api.misc.MatchInfo
import cc.maxmc.msm.parent.database.SQLDatabase
import java.util.concurrent.ConcurrentHashMap
object MatchManager {
private val matchMap = ConcurrentHashMap<Int, MatchInfo>()
fun requestMatch(type: String, players: List<String>): MatchInfo {
val server = ServerManager.consumeServer(type)
val id = SQLDatabase.recordMatch(type, players)
val match = MatchInfo(id, server)
matchMap[id] = match
return match
}
fun getMatchById(id: Int): MatchInfo? {
return matchMap[id]
}
fun endMatch(id: Int) {
val match = getMatchById(id) ?: throw IllegalStateException("Match does not exist.")
ServerManager.endServer(match.server.uid)
SQLDatabase.endMatch(id)
}
}

View File

@ -1,17 +1,50 @@
package cc.maxmc.msm.parent.manager package cc.maxmc.msm.parent.manager
import cc.maxmc.msm.api.misc.ServerInfo import cc.maxmc.msm.api.misc.ServerInfo
import cc.maxmc.msm.common.utils.log
import cc.maxmc.msm.common.utils.pluginScope
import kotlinx.coroutines.launch
import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
object ServerManager { object ServerManager {
val serverMap = ConcurrentHashMap<String, List<ServerInfo>>() private val serverMap = ConcurrentHashMap<String, MutableList<ServerInfo>>()
fun getAvailableServers(type: String): List<ServerInfo> { fun consumeServer(type: String): ServerInfo {
val servers = getAvailableServers(type)
val info = servers.first()
info.isAvailable = false
if (servers.size - 1 <= 2) {
pluginScope.launch {
log("§b| §7剩余 $type 服务器不足,正在启动新服务端。")
val server = requireServer(type)
log("§b| §7类型 $type 服务器启动成功: ${server.server}")
}
}
return info
}
fun endServer(uid: UUID) {
var result: ServerInfo? = null
val list = serverMap.values.find {
it.find { server -> server.uid == uid }?.let { server -> result = server; true } ?: false
} ?: throw IllegalStateException("Illegal state.")
list.remove(result)
}
fun getServerById(uid: UUID): ServerInfo? {
return serverMap.flatMap { it.value }.find { it.uid == uid }
}
private fun getAvailableServers(type: String): List<ServerInfo> {
return serverMap[type]!!.filter { it.isAvailable } return serverMap[type]!!.filter { it.isAvailable }
} }
private fun requireServer(type: String) { private suspend fun requireServer(type: String): ServerInfo {
val child = ChildManager.requestChild() val child = ChildManager.requestChild(type)
val list = serverMap.getOrPut(type) { ArrayList() }
val server = child.requestServer(type)
list.add(server)
return server
} }
} }

View File

@ -1,18 +1,33 @@
package cc.maxmc.msm.parent.misc package cc.maxmc.msm.parent.misc
import cc.maxmc.msm.api.misc.ServerInfo
import cc.maxmc.msm.common.network.BungeePacket import cc.maxmc.msm.common.network.BungeePacket
import cc.maxmc.msm.common.network.packet.CPacketRequestServer import cc.maxmc.msm.common.network.packet.CPacketRequestServer
import cc.maxmc.msm.common.network.packet.PPacketServerStarted
import cc.maxmc.msm.common.utils.awaitPacket import cc.maxmc.msm.common.utils.awaitPacket
import io.netty.channel.Channel import io.netty.channel.Channel
import java.net.InetSocketAddress
import java.util.*
class ChildBungee(val channel: Channel, var ports: Set<Int>, var usedPorts: MutableSet<Int> = HashSet()) { class ChildBungee(
val channel: Channel, val ports: Set<Int>, val types: Set<String>, val usedPorts: MutableSet<Int> = HashSet()
) {
private fun sendPacket(packet: BungeePacket) { private fun sendPacket(packet: BungeePacket) {
channel.writeAndFlush(packet) channel.writeAndFlush(packet)
} }
suspend fun requestServer(type: String) { suspend fun requestServer(type: String): ServerInfo {
sendPacket(CPacketRequestServer(type)) val uid = UUID.randomUUID()
awaitPacket() val server = ServerInfo(
uid,
"${(channel.remoteAddress() as InetSocketAddress).hostString}:${getAvailablePorts().first()}"
)
val cPacket = CPacketRequestServer(type, server)
sendPacket(cPacket)
val packet = awaitPacket(PPacketServerStarted::class.java) { ch, packet ->
ch == channel && packet.serverInfo == server
}
return packet.serverInfo
} }
fun getAvailablePorts() = ports - usedPorts fun getAvailablePorts() = ports - usedPorts

View File

@ -3,6 +3,8 @@ package cc.maxmc.msm.parent.netty
import cc.maxmc.msm.common.network.netty.NetworkRegistry import cc.maxmc.msm.common.network.netty.NetworkRegistry
import cc.maxmc.msm.common.utils.log import cc.maxmc.msm.common.utils.log
import cc.maxmc.msm.common.utils.pipelineInit import cc.maxmc.msm.common.utils.pipelineInit
import cc.maxmc.msm.parent.manager.MatchManager
import cc.maxmc.msm.parent.manager.ServerManager
import cc.maxmc.msm.parent.settings.Settings import cc.maxmc.msm.parent.settings.Settings
import io.netty.bootstrap.ServerBootstrap import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelFutureListener import io.netty.channel.ChannelFutureListener
@ -14,11 +16,8 @@ object NetManager {
private val childGroup = NioEventLoopGroup() private val childGroup = NioEventLoopGroup()
fun startServer() { fun startServer() {
ServerBootstrap() ServerBootstrap().channel(NioServerSocketChannel::class.java).group(parentGroup, childGroup)
.channel(NioServerSocketChannel::class.java) .childHandler(pipelineInit(NetworkRegistry.PacketDirection.PARENT_BOUND)).bind(Settings.serverPort)
.group(parentGroup, childGroup)
.childHandler(pipelineInit(NetworkRegistry.PacketDirection.PARENT_BOUND))
.bind(Settings.serverPort)
.addListener(ChannelFutureListener { .addListener(ChannelFutureListener {
val result = it.cause() ?: return@ChannelFutureListener log( val result = it.cause() ?: return@ChannelFutureListener log(
"§a| §7集群主服务端启动成功. ${ "§a| §7集群主服务端启动成功. ${
@ -33,5 +32,4 @@ object NetManager {
parentGroup.shutdownGracefully().sync() parentGroup.shutdownGracefully().sync()
childGroup.shutdownGracefully().sync() childGroup.shutdownGracefully().sync()
} }
} }

View File

@ -4,7 +4,7 @@ import cc.maxmc.msm.parent.settings.SettingsReader.config
object Settings { object Settings {
val serverPort val serverPort
get() = config.getInt("server_port", 25566) get() = config.getInt("manage_port", 23333)
object Database { object Database {
val address: String val address: String

View File

@ -1,4 +1,7 @@
serverPort: 12345 # 子BC连接主BC的端口
managePort: 23333
# 数据库配置
database: database:
address: 127.0.0.1 address: 127.0.0.1
port: 3306 port: 3306