Class MapleServerHandler

java.lang.Object
io.netty.channel.ChannelHandlerAdapter
io.netty.channel.ChannelInboundHandlerAdapter
io.netty.channel.ChannelDuplexHandler
handling.MapleServerHandler
All Implemented Interfaces:
io.netty.channel.ChannelHandler, io.netty.channel.ChannelInboundHandler, io.netty.channel.ChannelOutboundHandler

public class MapleServerHandler extends io.netty.channel.ChannelDuplexHandler
三大伺服器共用的 Netty 封包分派器與連線生命週期處理器。

本類別是 LoginServer/ChannelServer/CashShopServer 三者共用的單一 ChannelDuplexHandler, 由建構子帶入的 ServerType 與頻道編號區分身分。其在整體架構中扮演封包進出的中樞:

  • 連線建立時建立 MapleClient、送出 AES-OFB 握手(MapleAESOFBLoginPacket), 並將之掛在 MapleClient.CLIENT_KEY channel 屬性上。
  • 入站 channelRead 讀取 short opcode,線性掃描 RecvPacketOpcode 後交由靜態 handlePacket 大 switch 分派到 handling.channel.handler.*handling.login.handler.*handling.cashshop.handler.* 的各處理器。
  • 連線中斷時於 channelInactive 觸發 MapleClient 斷線清理,並維護封包日誌與 IP 記錄。
  • Nested Class Summary

    Nested classes/interfaces inherited from interface io.netty.channel.ChannelHandler

    io.netty.channel.ChannelHandler.Sharable
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static boolean
    全域封包記錄總開關。
  • Constructor Summary

    Constructors
    Constructor
    Description
    MapleServerHandler(int channel, ServerType type)
    建立一個繫結到特定伺服器角色的封包處理器。
  • Method Summary

    Modifier and Type
    Method
    Description
    static void
    addIP(String theIP)
    為指定 IP 開啟一個專屬封包記錄檔,並登記到內部的 IP→Writer 對照表。
    void
    channelActive(io.netty.channel.ChannelHandlerContext ctx)
    連線建立回呼:執行 IP 連線頻率防護、AES-OFB 握手與 MapleClient 初始化。
    void
    channelInactive(io.netty.channel.ChannelHandlerContext ctx)
    連線中斷回呼:觸發 MapleClient 斷線清理並維護斷線統計與記錄。
    void
    channelRead(io.netty.channel.ChannelHandlerContext ctx, Object message)
    入站封包進入點:解析 opcode、比對 RecvPacketOpcode 後分派至 handlePacket(RecvPacketOpcode, LittleEndianAccessor, MapleClient, boolean)
    void
    exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause)
    Netty 例外回呼,目前刻意為空(不傳播、不關閉連線、不記錄)。
    static final void
    主封包分派器:依 opcode 將已解析的封包交給對應的處理器類別。
    static final void
    初始化封包記錄/監控子系統,於伺服器啟動時呼叫一次。
    static void
    log(String packet, String op, MapleClient c, io.netty.channel.Channel io)
    將一筆封包記錄寫入記憶體中的環狀緩衝區 Packet_Log(不落地)。
    static void
    重新載入 LogIPs.txt 中列出的受監控 IP 清單。
    void
    userEventTriggered(io.netty.channel.ChannelHandlerContext ctx, Object status)
    使用者事件回呼,用以處理閒置逾時:對在線 client 送出 ping,否則關閉連線。
    void
    將記憶體封包緩衝區落地成一般記錄檔。
    void
    writeLog(boolean crash)
    將記憶體封包緩衝區 Packet_Log 落地寫入檔案,並重置緩衝區。

    Methods inherited from class io.netty.channel.ChannelDuplexHandler

    bind, close, connect, deregister, disconnect, flush, read, write

    Methods inherited from class io.netty.channel.ChannelInboundHandlerAdapter

    channelReadComplete, channelRegistered, channelUnregistered, channelWritabilityChanged

    Methods inherited from class io.netty.channel.ChannelHandlerAdapter

    ensureNotSharable, handlerAdded, handlerRemoved, isSharable

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface io.netty.channel.ChannelHandler

    handlerAdded, handlerRemoved
  • Field Details

    • Log_Packets

      public static boolean Log_Packets
      全域封包記錄總開關。

      true 時,頻道/商城伺服器收發的封包會被緩衝進 Packet_Log 環狀緩衝區, 並在短時間內大量斷線(疑似當機潮)時由 writeLog(boolean) 落地成檔。 預設關閉以避免額外開銷;可由 initiate() 在記錄檔額度用罄時自動關回 false。 此旗標非執行緒安全,僅作為簡易布林開關使用。

  • Constructor Details

    • MapleServerHandler

      public MapleServerHandler(int channel, ServerType type)
      建立一個繫結到特定伺服器角色的封包處理器。

      三大伺服器(登入/頻道/商城)共用本類別,靠 channelServerType 區分身分:頻道伺服器傳入 0 起的頻道編號(channel > -1), 登入/商城伺服器傳入 -1 並以 type 區別。

      注意 type 寫入的是靜態欄位 type(並非實例欄位), 為此既有實作的既定行為。

      Parameters:
      channel - 頻道編號;登入/商城伺服器為 -1
      type - 伺服器類型(登入/頻道/商城)
  • Method Details

    • reloadLoggedIPs

      public static void reloadLoggedIPs()
      重新載入 LogIPs.txt 中列出的受監控 IP 清單。

      先關閉並清空目前所有開啟的封包記錄 Writer(每個 IP 對應一個檔案, 寫入 === Closing Log === 後 flush/close),再逐行掃描 LogIPs.txt (以 Encoding.DEFAULT 編碼讀取),對每個非空白行呼叫 addIP(String) 重新建立記錄檔。

      有副作用:關閉舊檔案 Writer、建立新檔案。任何 I/O 例外均被吞掉並記錄至 log。 此方法並未加鎖(原始碼註解 Screw locking. Doesn't matter.)。

    • addIP

      public static void addIP(String theIP)
      為指定 IP 開啟一個專屬封包記錄檔,並登記到內部的 IP→Writer 對照表。

      以附加模式(append)建立 PacketLog_<theIP>.txtEncoding.DEFAULT 編碼), 寫入 === Creating Log === 標頭後 flush,再放入 logIPMap。 之後該 IP 的連線進出與封包都會被寫入此檔(見 channelReadchannelActive)。

      有副作用:建立/開啟檔案並持有其 WriterIOException 會被吞掉並轉交 FileoutputUtil.outputFileError(String, Throwable) 記錄。

      Parameters:
      theIP - 要開始監控記錄的 IP 字串
    • log

      public static void log(String packet, String op, MapleClient c, io.netty.channel.Channel io)
      將一筆封包記錄寫入記憶體中的環狀緩衝區 Packet_Log(不落地)。

      Packet_Log_Lock 寫鎖保護下執行:當緩衝區達到上限 Log_Size 時, 移除最舊一筆並重用其 MapleServerHandler.LoggedPacket 物件(呼叫 setInfo 而非 new,以減少配置), 否則新建一筆。記錄內容含遠端位址、帳號 ID/名稱、角色名、地圖 ID 與目前對話中的 NPC ID, 皆對 null 做防護(缺值以 [Null]-1 表示)。

      此方法執行緒安全(持 Packet_Log_Lock 寫鎖);資料僅存於記憶體, 須由 writeLog(boolean) 才會寫成檔案。

      Parameters:
      packet - 封包內容字串(通常為十六進位傾印)
      op - 封包對應的 opcode 名稱
      c - 來源 MapleClient,可為 null(缺值欄位以 [Null] 代入)
      io - 來源 Netty Channel,用以取得遠端位址
    • writeLog

      public void writeLog()
      將記憶體封包緩衝區落地成一般記錄檔。

      等同 writeLog(false)(非當機情境)。詳見 writeLog(boolean)

    • writeLog

      public void writeLog(boolean crash)
      將記憶體封包緩衝區 Packet_Log 落地寫入檔案,並重置緩衝區。

      Packet_Log_Lock 讀鎖保護下執行:以附加模式開啟 Packet/PacketLog<index>.txt(當 crashtrue 時檔名後綴 _DC.txtEncoding.DEFAULT 編碼),逐筆寫入 MapleServerHandler.LoggedPacket,再附上一行統計摘要 (斷線數 numDC 與時間窗)。寫完清空 Packet_Log、遞增檔案索引; 索引超過 Packet_Log_Size 時歸零並將 Log_Packets 關回 false(停止記錄)。

      有副作用:寫檔、輸出 log、清空緩衝區、修改靜態索引與 Log_Packets 開關。 IOException 會被吞掉僅記 log。

      Parameters:
      crash - 是否為當機/大量斷線情境(true 時檔名加 _DC 後綴)
    • initiate

      public static final void initiate()
      初始化封包記錄/監控子系統,於伺服器啟動時呼叫一次。

      流程:

      • 呼叫 reloadLoggedIPs() 載入受監控 IP 清單。
      • 填入兩個過濾用 EnumSetblocked(高頻雜訊封包,如移動/PONG, 完全不記錄)與 sBlocked(僅排除於 Packet_Log 落地記錄之外的封包)。
      • Log_Packets 已啟用,掃描尋找第一個尚未使用的記錄檔索引; 若 25 個檔名皆已存在則將 Log_Packets 關回 false(不再記錄)。

      有副作用:修改靜態的 blockedsBlocked 集合、Packet_Log_Index 與 可能修改 Log_Packets。應在啟動期單執行緒呼叫。

    • exceptionCaught

      public void exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause) throws Exception
      Netty 例外回呼,目前刻意為空(不傳播、不關閉連線、不記錄)。

      原始實作(取得 MapleClientlog.error)已整段註解保留於方法本體; 現行行為是吞掉 pipeline 中的所有例外。

      Specified by:
      exceptionCaught in interface io.netty.channel.ChannelHandler
      Specified by:
      exceptionCaught in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      exceptionCaught in class io.netty.channel.ChannelInboundHandlerAdapter
      Parameters:
      ctx - 觸發例外的 channel 上下文
      cause - 例外原因
      Throws:
      Exception - 介面所宣告;本實作不會拋出
    • channelActive

      public void channelActive(io.netty.channel.ChannelHandlerContext ctx) throws Exception
      連線建立回呼:執行 IP 連線頻率防護、AES-OFB 握手與 MapleClient 初始化。

      主要步驟:

      有副作用:可能關閉 channel、修改 IP 追蹤/封鎖狀態、消耗登入授權、發送握手封包、寫記錄檔。 於 Netty I/O 執行緒上執行。

      Specified by:
      channelActive in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      channelActive in class io.netty.channel.ChannelInboundHandlerAdapter
      Parameters:
      ctx - 新建連線的 channel 上下文
      Throws:
      Exception - 寫檔或下游呼叫可能拋出
    • channelInactive

      public void channelInactive(io.netty.channel.ChannelHandlerContext ctx) throws Exception
      連線中斷回呼:觸發 MapleClient 斷線清理並維護斷線統計與記錄。

      僅對已登入(accID > 0)的 client 動作:

      • 若啟用封包記錄且非「換頻道」這類正常轉移狀態,記下一筆 CLOSED 封包; 一分鐘內斷線數 numDC 累計逾 100 即呼叫 writeLog() 落地並重置計數。
      • 呼叫 MapleClient.disconnect(boolean, boolean)(商城或 cs 時帶旗標)。
      • 若玩家仍在線且處於商城/交易/換頻道/登入轉移等過場狀態, 依 World.Find.findChannel(int) 找出所在頻道,從對應 ChannelServer 移除玩家; 若為商城(-10)則自商城與 MTS 玩家儲存區登出。
      • channel == 0 時自 LoginServer 移除 client。
      • finally 中關閉 channel 並移除 CLIENT_KEY 屬性。

      有副作用:跨伺服器移除玩家、寫記錄、修改靜態斷線統計、關閉 channel。於 Netty I/O 執行緒上執行。 末段呼叫 super.channelInactive(ctx) 續傳事件。

      Specified by:
      channelInactive in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      channelInactive in class io.netty.channel.ChannelInboundHandlerAdapter
      Parameters:
      ctx - 已中斷連線的 channel 上下文
      Throws:
      Exception - 下游清理或 super 呼叫可能拋出
    • userEventTriggered

      public void userEventTriggered(io.netty.channel.ChannelHandlerContext ctx, Object status) throws Exception
      使用者事件回呼,用以處理閒置逾時:對在線 client 送出 ping,否則關閉連線。

      由 pipeline 中的閒置偵測(IdleStateEvent)觸發。取得 MapleClient.CLIENT_KEY 上的 client:非 null 時呼叫 MapleClient.sendPing() 以驗證連線存活;為 null 時直接關閉 channel 並返回(不續傳事件)。 否則末段呼叫 super.userEventTriggered(ctx, status)

      有副作用:可能發送 ping 封包或關閉 channel。於 Netty I/O 執行緒上執行。

      Specified by:
      userEventTriggered in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      userEventTriggered in class io.netty.channel.ChannelInboundHandlerAdapter
      Parameters:
      ctx - channel 上下文
      status - 觸發的使用者事件物件
      Throws:
      Exception - 下游呼叫或 super 可能拋出
    • channelRead

      public void channelRead(io.netty.channel.ChannelHandlerContext ctx, Object message)
      入站封包進入點:解析 opcode、比對 RecvPacketOpcode 後分派至 handlePacket(RecvPacketOpcode, LittleEndianAccessor, MapleClient, boolean)

      message 為已解密/解碼的 byte[](由 server.netty.MaplePacketDecoder 產生)。 流程:

      • 包裝為 LittleEndianAccessor;長度不足 2 或 client 不在接收狀態(!isReceiving)即丟棄。
      • 讀取 short 包頭,線性掃描整個 RecvPacketOpcode 列舉尋找相符值 (此為既有設計,非雜湊查表)。
      • 命中後:對 NeedsChecking() 的封包驗證登入狀態;視 monitoredLog_Packets 與過濾集合決定是否記錄;接著呼叫 handlePacket(recv, slea, c, type == 商城伺服器) 真正處理。
      • 處理後若 World.Find.findChannel(int)-1(假斷線)且非 PLAYER_LOGGEDIN, 更新登入狀態為未登入並關閉連線。
      • 受監控 IP 會在處理追記封包內容(登入封包另加帳號/IGN/時間標頭)。

      例外處理:NegativeArraySizeExceptionArrayIndexOutOfBoundsException 與其他 Exception 皆被吞掉並寫入 PacketEx_Log(前者於固定 IV 模式下連 log 都略過)。 未對應的包頭在 ServerConstants.LOG_UNHandle_PACKETS 開啟時記到 UnknownPacket_Log

      有副作用:分派處理(可能改變大量遊戲狀態、發送封包)、寫記錄檔、可能關閉連線。 於 Netty I/O 執行緒上執行。

      Specified by:
      channelRead in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      channelRead in class io.netty.channel.ChannelInboundHandlerAdapter
      Parameters:
      ctx - 來源 channel 上下文
      message - 已解密的封包位元組(byte[]);null 或無 channel 時直接返回
    • handlePacket

      public static final void handlePacket(RecvPacketOpcode header, LittleEndianAccessor slea, MapleClient c, boolean cs) throws Exception
      主封包分派器:依 opcode 將已解析的封包交給對應的處理器類別。

      一個涵蓋全部 RecvPacketOpcode 的大型 switch,涵蓋登入(CharLoginHandler)、 玩家動作/戰鬥(PlayerHandlerPlayersHandler)、物品(InventoryHandlerItemMakerHandler)、怪物(MobHandler)、NPC/商店(NPCHandler)、 召喚(SummonHandler)、寵物(PetHandler)、聊天(ChatHandler)、 組隊/公會/聯盟/家族(PartyHandlerGuildHandlerAllianceHandlerFamilyHandler)、跨伺服器轉移(InterServerHandler)與商城/MTS (CashShopOperationMTSOperation)等。部分 case 在分派前會先從 slea 讀取參數或呼叫 c.getPlayer().updateTick(...)。未對應的 opcode 走 default 記一行 [UNHANDLED] log。

      呼叫端 channelRead 已捕捉並吞掉本方法拋出的例外,故此處不另行防護。 有副作用:幾乎所有遊戲狀態變更與封包回送都由此分派觸發。

      Parameters:
      header - 已比對出的接收 opcode
      slea - 封包資料讀取器(多數 case 會自其續讀參數)
      c - 來源 MapleClient
      cs - 是否來自商城伺服器(type == 商城伺服器
      Throws:
      Exception - 任一下游處理器均可能拋出(由呼叫端統一處理)