phdljr 69aa4f5bfe56497781928ca9e4814c48
서론
멀티 서버에 접속한 모든 사용자에게 사용자의 메세지를 전달하는 확성기 기능을 가진 마인크래프트 플러그인을 개발하게 되었다.
서버의 구조는 다음과 같이 구성돼있다.
보통 확성기 기능은 해당 서버의 broadcast 메서드를 통해 구현하는 듯 하지만, 사용자가 접속한 서버에서만 전체 전달이 된다.
어느 서버에 접속해있든, 확성기를 사용하면 모든 서버에 메세지가 전달되도록 하는 방법이 뭐가 없을까 싶어서 구글링을 해보니, 플러그인 메세지 채널이라는 기능을 알게 되었다.
이에 대해서 좀 더 알아보는 시간을 가져보도록 하겠다.
Plugin Message Channel
- 서로 다른 플러그인 간, 또는 서버와 클라이언트 간 커스텀 메시지를 주고받기 위해 사용하는 비동기 통신 방식
- RabbitMQ, Kafka처럼 메세지 브로커 역할을 하는 것처럼 보임
- 채널이라는 통로를 통해 프록시 서버와 백엔드 서버 간 메세지를 주고받을 수 있음
- 이미 만들어진 채널인
BungeeCord
을 활용할 수도 있고, 개발자가 직접 채널을 새로 만들어서 통신할 수도 있음
채널 이름 규칙
- 1.13 버전 이후 기준,
(namespace):(name)
형태를 갖추는 것을 추천- 다른 플러그인과의 채널 명 충돌을 방지하기 위한 수단
- Velocity API에선
ChannelIdentifier
를 만들기 위해, 위와 같은 형태로 사용 중 - 특이 케이스로,
BungeeCord
채널은 내부적으로bungeecord:main
로 변환됨
BungeeCord 채널 예제 코드 - 점프한 유저를 특정 서버 이동
- 사용자가 점프를 하면 wild 서버로 이동시키는 코드
- 점프를 할 시, BungeeCord 채널로 Connect 프로토콜을 활용하여 wild 서버로 이동시키도록 채널로 메세지 전송
class PluginMessageChannel : JavaPlugin(), Listener {
private val BUNGEE_CORD = "BungeeCord"
override fun onEnable() {
// 해당 클래스를 이벤트 리스너로 등록
server.pluginManager.registerEvents(this, this)
// BungeeCord 채널로 메세지를 보낼 수 있도록 설정
server.messenger.registerOutgoingPluginChannel(this, BUNGEE_CORD)
}
// 플레이어가 점프를 하면, wild 서버로 이동시키도록 BungeeCord 채널로 메세지 전송
// BungeeCord 채널에서만 적용되는 프로토콜이 존재함으로, 이를 활용한 모습을 나타냄
@EventHandler
fun onPlayerJump(event: PlayerJumpEvent) {
val player = event.getPlayer()
val out = ByteStreams.newDataOutput()
out.writeUTF("Connect")
out.writeUTF("wild")
player.sendPluginMessage(this, BUNGEE_CORD, out.toByteArray())
}
override fun onDisable() {}
}
BungeeCord 채널 예제 코드 - 특정 서버 유저 수 조회
// 플러그인 메세지 응답을 조회하기 위해선, PluginMessageListener를 구현해야 한다.
class PluginMessageChannel : JavaPlugin(), PluginMessageListener {
private val BUNGEE_CORD = "BungeeCord"
override fun onEnable() {
server.pluginManager.registerEvents(this, this)
server.messenger.registerOutgoingPluginChannel(this, BUNGEE_CORD)
// 해당 클래스에서 BungeeCord 채널 메세지를 수신할 수 있도록 등록
server.messenger.registerIncomingPluginChannel(this, BUNGEE_CORD, this)
// 메세지를 전송하기 위해선 플레이어 객체가 필수이다.
// 해당 예제에선 ... 으로 편의상 대체한다.
val player = ...
val output = ByteStreams.newDataOutput()
out.writeUTF("PlayerCount")
out.writeUTF("lobby")
player.sendPluginMessage(this, BUNGEE_CORD, output.toByteArray())
}
// PluginMessageListener 구현부
// 해당 메서드에서 채널에 대한 메세지를 조회한다.
override fun onPluginMessageReceived(channel: String, player: Player, message: ByteArray) {
if (channel != BUNGEE_CORD) {
return
}
val input = ByteStreams.newDataInput(message)
val subchannel = input.readUTF()
if (subchannel == "PlayerCount") {
val server = input.readUTF()
val playerCount = input.readInt()
}
}
override fun onDisable() {}
}
커스텀 채널 예제 코드 - 모든 서버에 채팅을 전송하는 확성기
- 개발자가 임의로 채널 명을 정할 수 있음
- 예제에선
broadcast:main
채널명을 갖도록 진행
Velocity API 플러그인
@Plugin(
id = "broadcast",
name = "broadcast",
version = BuildConstants.VERSION
)
class Broadcast @Inject constructor(
private val logger: Logger,
private val proxy: ProxyServer
) {
// broadcast:main 채널 정보를 담은 객체
private val channel: ChannelIdentifier = MinecraftChannelIdentifier.create("broadcast", "main")
@Subscribe
fun onProxyInitialize(event: ProxyInitializeEvent) {
proxy.channelRegistrar.register(channel)
logger.info("Shout channel registered")
}
// 플러그인 채널 메세지 수신 리스너
@Subscribe
fun onPluginMessage(event: PluginMessageEvent) {
// broadcast:main 채널이 아니라면 무시
if (event.identifier != channel)
return
try {
val dataStream = DataInputStream(ByteArrayInputStream(event.data))
val message = dataStream.readUTF()
val broadcast = Component.text("[확성기] $message", NamedTextColor.LIGHT_PURPLE)
// 프록시에 연결된 모든 서버에 접속한 유저들에게 메세지 전송
proxy.allPlayers.forEach { it.sendMessage(broadcast) }
logger.info("Broadcasted shout: $message")
} catch (e: Exception) {
logger.error("메시지 수신 중 오류 발생", e)
}
}
}
Paper API 플러그인
class BroadcastCommand(val plugin: JavaPlugin): CommandExecutor {
override fun onCommand(
sender: CommandSender,
command: Command,
label: String,
args: Array<out String>
): Boolean {
if (sender !is Player) {
sender.sendMessage("[알림] 플레이어만 사용할 수 있습니다.")
return true
}
if (args.isEmpty()) {
sender.sendMessage("[알림] 사용법: /확성기 <메시지>")
return true
}
val message: String? = args.joinToString(" ")
// 플러그인 메시지 전송
try {
val output = ByteArrayOutputStream()
val data = DataOutputStream(output)
data.writeUTF(sender.name + ": " + message) // 메시지 내용
sender.sendPluginMessage(plugin, "broadcast:main", output.toByteArray())
sender.sendMessage("[알림] 확성기 메시지를 전송했습니다!")
} catch (e: IOException) {
sender.sendMessage("[알림] 전송 실패: " + e.message)
}
return true
}
}
결과
예제 코드 - BungeeCord의 MessageRaw를 활용한 모든 서버에 채팅을 전송하는 확성기
- 사실,
BungeeCord
채널의MessageRaw
타입을 사용하면 프록시 플러그인 없이도 가능
댓글이 없습니다.
새로운 댓글을 등록해 주세요!