개인 자료란 (JE)

  서버 커뮤니티

Profile 효자이씨 대표칭호 없음

phdljr 69aa4f5bfe56497781928ca9e4814c48

Profile

커뮤니티 소통 개발하기 플러그인

서버간 통신을 위한 플러그인 메세지 채널

2025.05.15 조회 수 251 추천 수 0

서론

멀티 서버에 접속한 모든 사용자에게 사용자의 메세지를 전달하는 확성기 기능을 가진 마인크래프트 플러그인을 개발하게 되었다.

서버의 구조는 다음과 같이 구성돼있다.

보통 확성기 기능은 해당 서버의 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타입을 사용하면 프록시 플러그인 없이도 가능


결론

플러그인 메세지 채널을 활용하여 프록시 서버와 백엔드 서버간 통신이 가능하다.


참조

https://docs.papermc.io/paper/dev/plugin-messaging/

Warning
댓글이 없습니다.

새로운 댓글을 등록해 주세요!

뉴스 및 창작물
/files/thumbnails/482/063/004/262x150.crop.jpg?20250619235858

업데이트

마인크래프트 자바 1.21.6 (Minecraft Java Edition 1.21.6)

updater

2025-06-18

1

/files/thumbnails/520/060/004/262x150.crop.jpg?20250612233233

업데이트

마인크래프트 자바 1.21.6 릴리스 후보 1 한국어 번역본 (Minecraft 1.21.6 Release Candidate 1)

updater

2025-06-12

0

/files/thumbnails/267/050/004/262x150.crop.jpg?20250611010233

업데이트

마인크래프트 자바 1.21.6 프리릴리스 1 한국어 번역본 (Minecraft 1.21.6 Pre-Release 1)

updater

2025-05-28

1

/files/thumbnails/166/043/004/262x150.crop.jpg?20250603112940

업데이트

마인크래프트 자바 스냅숏 25w20a 한국어 번역본 (Minecraft Snapshot 25w20a)

updater

2025-05-14

0

/files/thumbnails/255/028/004/262x150.crop.jpg?20250603113100

업데이트

마인크래프트 자바 스냅숏 25w16a 한국어 번역본 (Minecraft Snapshot 25w16a)

updater

2025-04-16

0