Class MapleServerHandler
- All Implemented Interfaces:
io.netty.channel.ChannelHandler, io.netty.channel.ChannelInboundHandler, io.netty.channel.ChannelOutboundHandler
本類別是 LoginServer/ChannelServer/CashShopServer 三者共用的單一 ChannelDuplexHandler,
由建構子帶入的 ServerType 與頻道編號區分身分。其在整體架構中扮演封包進出的中樞:
- 連線建立時建立
MapleClient、送出 AES-OFB 握手(MapleAESOFB/LoginPacket), 並將之掛在MapleClient.CLIENT_KEYchannel 屬性上。 - 入站
channelRead讀取shortopcode,線性掃描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 -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic void為指定 IP 開啟一個專屬封包記錄檔,並登記到內部的 IP→Writer對照表。voidchannelActive(io.netty.channel.ChannelHandlerContext ctx) 連線建立回呼:執行 IP 連線頻率防護、AES-OFB 握手與MapleClient初始化。voidchannelInactive(io.netty.channel.ChannelHandlerContext ctx) 連線中斷回呼:觸發MapleClient斷線清理並維護斷線統計與記錄。voidchannelRead(io.netty.channel.ChannelHandlerContext ctx, Object message) 入站封包進入點:解析 opcode、比對RecvPacketOpcode後分派至handlePacket(RecvPacketOpcode, LittleEndianAccessor, MapleClient, boolean)。voidexceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause) Netty 例外回呼,目前刻意為空(不傳播、不關閉連線、不記錄)。static final voidhandlePacket(RecvPacketOpcode header, LittleEndianAccessor slea, MapleClient c, boolean cs) 主封包分派器:依 opcode 將已解析的封包交給對應的處理器類別。static final voidinitiate()初始化封包記錄/監控子系統,於伺服器啟動時呼叫一次。static voidlog(String packet, String op, MapleClient c, io.netty.channel.Channel io) 將一筆封包記錄寫入記憶體中的環狀緩衝區Packet_Log(不落地)。static void重新載入LogIPs.txt中列出的受監控 IP 清單。voiduserEventTriggered(io.netty.channel.ChannelHandlerContext ctx, Object status) 使用者事件回呼,用以處理閒置逾時:對在線 client 送出 ping,否則關閉連線。voidwriteLog()將記憶體封包緩衝區落地成一般記錄檔。voidwriteLog(boolean crash) 將記憶體封包緩衝區Packet_Log落地寫入檔案,並重置緩衝區。Methods inherited from class io.netty.channel.ChannelDuplexHandler
bind, close, connect, deregister, disconnect, flush, read, writeMethods inherited from class io.netty.channel.ChannelInboundHandlerAdapter
channelReadComplete, channelRegistered, channelUnregistered, channelWritabilityChangedMethods inherited from class io.netty.channel.ChannelHandlerAdapter
ensureNotSharable, handlerAdded, handlerRemoved, isSharableMethods inherited from class Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods 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
建立一個繫結到特定伺服器角色的封包處理器。三大伺服器(登入/頻道/商城)共用本類別,靠
channel與ServerType區分身分:頻道伺服器傳入0起的頻道編號(channel > -1), 登入/商城伺服器傳入-1並以type區別。注意
type寫入的是靜態欄位type(並非實例欄位), 為此既有實作的既定行為。- Parameters:
channel- 頻道編號;登入/商城伺服器為-1type- 伺服器類型(登入/頻道/商城)
-
-
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
為指定 IP 開啟一個專屬封包記錄檔,並登記到內部的 IP→Writer對照表。以附加模式(append)建立
PacketLog_<theIP>.txt(Encoding.DEFAULT編碼), 寫入=== Creating Log ===標頭後 flush,再放入logIPMap。 之後該 IP 的連線進出與封包都會被寫入此檔(見channelRead/channelActive)。有副作用:建立/開啟檔案並持有其
Writer。IOException會被吞掉並轉交FileoutputUtil.outputFileError(String, Throwable)記錄。- Parameters:
theIP- 要開始監控記錄的 IP 字串
-
log
將一筆封包記錄寫入記憶體中的環狀緩衝區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- 來源 NettyChannel,用以取得遠端位址
-
writeLog
public void writeLog()將記憶體封包緩衝區落地成一般記錄檔。等同
writeLog(false)(非當機情境)。詳見writeLog(boolean)。 -
writeLog
public void writeLog(boolean crash) 將記憶體封包緩衝區Packet_Log落地寫入檔案,並重置緩衝區。在
Packet_Log_Lock讀鎖保護下執行:以附加模式開啟Packet/PacketLog<index>.txt(當crash為true時檔名後綴_DC.txt,Encoding.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 清單。 - 填入兩個過濾用
EnumSet:blocked(高頻雜訊封包,如移動/PONG, 完全不記錄)與sBlocked(僅排除於Packet_Log落地記錄之外的封包)。 - 若
Log_Packets已啟用,掃描尋找第一個尚未使用的記錄檔索引; 若 25 個檔名皆已存在則將Log_Packets關回false(不再記錄)。
有副作用:修改靜態的
blocked/sBlocked集合、Packet_Log_Index與 可能修改Log_Packets。應在啟動期單執行緒呼叫。 - 呼叫
-
exceptionCaught
public void exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause) throws Exception Netty 例外回呼,目前刻意為空(不傳播、不關閉連線、不記錄)。原始實作(取得
MapleClient並log.error)已整段註解保留於方法本體; 現行行為是吞掉 pipeline 中的所有例外。- Specified by:
exceptionCaughtin interfaceio.netty.channel.ChannelHandler- Specified by:
exceptionCaughtin interfaceio.netty.channel.ChannelInboundHandler- Overrides:
exceptionCaughtin classio.netty.channel.ChannelInboundHandlerAdapter- Parameters:
ctx- 觸發例外的 channel 上下文cause- 例外原因- Throws:
Exception- 介面所宣告;本實作不會拋出
-
channelActive
連線建立回呼:執行 IP 連線頻率防護、AES-OFB 握手與MapleClient初始化。主要步驟:
- IP 防洪:已封鎖 IP 直接關閉;同一 IP 於 2 秒內重複連線累計計數, 達 10 次即加入封鎖名單並關閉連線(超過 20 秒的間隔則重置計數)。
- 伺服器就緒/授權檢查:頻道伺服器須未關機且該 IP 已通過登入授權
(
LoginServer.containsIPAuth(String));商城/登入伺服器須未關機。任一不符即關閉連線。 通過後呼叫LoginServer.removeIPAuth(String)消耗該授權。 - 握手:產生收/送向量(
ServerConstants.Use_Fixed_IV為真時用固定 IV), 建立MapleClient(送密器版本0xFFFF - MAPLE_VERSION、收密器版本MAPLE_VERSION), 設定解碼狀態,送出LoginPacket.getHello(short, byte[], byte[])並把 client 掛上MapleClient.CLIENT_KEYchannel 屬性。 - 非固定 IV 時重載
RecvPacketOpcode/SendPacketOpcode值。 - 受監控 IP(見
addIP(String))會被標記monitored並寫入登入記錄行。
有副作用:可能關閉 channel、修改 IP 追蹤/封鎖狀態、消耗登入授權、發送握手封包、寫記錄檔。 於 Netty I/O 執行緒上執行。
- Specified by:
channelActivein interfaceio.netty.channel.ChannelInboundHandler- Overrides:
channelActivein classio.netty.channel.ChannelInboundHandlerAdapter- Parameters:
ctx- 新建連線的 channel 上下文- Throws:
Exception- 寫檔或下游呼叫可能拋出
-
channelInactive
連線中斷回呼:觸發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:
channelInactivein interfaceio.netty.channel.ChannelInboundHandler- Overrides:
channelInactivein classio.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:
userEventTriggeredin interfaceio.netty.channel.ChannelInboundHandler- Overrides:
userEventTriggeredin classio.netty.channel.ChannelInboundHandlerAdapter- Parameters:
ctx- channel 上下文status- 觸發的使用者事件物件- Throws:
Exception- 下游呼叫或super可能拋出
-
channelRead
入站封包進入點:解析 opcode、比對RecvPacketOpcode後分派至handlePacket(RecvPacketOpcode, LittleEndianAccessor, MapleClient, boolean)。message為已解密/解碼的byte[](由server.netty.MaplePacketDecoder產生)。 流程:- 包裝為
LittleEndianAccessor;長度不足 2 或 client 不在接收狀態(!isReceiving)即丟棄。 - 讀取
short包頭,線性掃描整個RecvPacketOpcode列舉尋找相符值 (此為既有設計,非雜湊查表)。 - 命中後:對
NeedsChecking()的封包驗證登入狀態;視monitored/Log_Packets與過濾集合決定是否記錄;接著呼叫handlePacket(recv, slea, c, type == 商城伺服器)真正處理。 - 處理後若
World.Find.findChannel(int)回-1(假斷線)且非PLAYER_LOGGEDIN, 更新登入狀態為未登入並關閉連線。 - 受監控 IP 會在處理後追記封包內容(登入封包另加帳號/IGN/時間標頭)。
例外處理:
NegativeArraySizeException/ArrayIndexOutOfBoundsException與其他Exception皆被吞掉並寫入PacketEx_Log(前者於固定 IV 模式下連 log 都略過)。 未對應的包頭在ServerConstants.LOG_UNHandle_PACKETS開啟時記到UnknownPacket_Log。有副作用:分派處理(可能改變大量遊戲狀態、發送封包)、寫記錄檔、可能關閉連線。 於 Netty I/O 執行緒上執行。
- Specified by:
channelReadin interfaceio.netty.channel.ChannelInboundHandler- Overrides:
channelReadin classio.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)、 玩家動作/戰鬥(PlayerHandler/PlayersHandler)、物品(InventoryHandler/ItemMakerHandler)、怪物(MobHandler)、NPC/商店(NPCHandler)、 召喚(SummonHandler)、寵物(PetHandler)、聊天(ChatHandler)、 組隊/公會/聯盟/家族(PartyHandler/GuildHandler/AllianceHandler/FamilyHandler)、跨伺服器轉移(InterServerHandler)與商城/MTS (CashShopOperation/MTSOperation)等。部分 case 在分派前會先從slea讀取參數或呼叫c.getPlayer().updateTick(...)。未對應的 opcode 走default記一行[UNHANDLED]log。呼叫端
channelRead已捕捉並吞掉本方法拋出的例外,故此處不另行防護。 有副作用:幾乎所有遊戲狀態變更與封包回送都由此分派觸發。- Parameters:
header- 已比對出的接收 opcodeslea- 封包資料讀取器(多數 case 會自其續讀參數)c- 來源MapleClientcs- 是否來自商城伺服器(type == 商城伺服器)- Throws:
Exception- 任一下游處理器均可能拋出(由呼叫端統一處理)
-