Files
test/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md
T

4.5 KiB

Bug: Client không nhận PROTOCOL_PLAYERLOGOUT sau khi logout

Tóm tắt

  • Triệu chứng: Server gửi PROTOCOL_PLAYERLOGOUT (69) sau khi client gửi lệnh logout, nhưng client không nhận được gói này (handler không chạy).
  • Nguyên nhân: Logic xử lý buffer nhận trong NetworkManager.ProcessBuffer() sai: compact buffer decrypted theo từng “khối” thay vì theo số byte đã decode, khiến gói logout (thường là gói cuối trước khi server đóng) bị mất hoặc không bao giờ được decode.
  • Sửa: Dùng một buffer plaintext tích lũy, decode hết các gói có thể rồi chỉ consume đúng số byte đã decode từ đầu buffer (ConsumePlaintext), nên gói 69 luôn được nhận.

1. Timeline khi logout

Thời gian →

Client:  [Gửi logout] -------- đợi -------- [Socket đóng / Disconnect]
                │                              │
Server:  [Nhận logout] → [Gửi PROTOCOL_PLAYERLOGOUT] → [Đóng connection]
                              │
                              └── Gói 69 (playerlogout) nằm trên đường truyền

Gói 69 có tới client (trong buffer TCP), nhưng code xử lý buffer cũ làm mất nó trước khi decode.


2. Code cũ: buffer bị “cắt” sai

Buffer giải mã _decryptedOctets coi như một hàng ô (mỗi ô = 1 byte):

Lần đọc 1 từ socket:
[ A ][ B ][ C ][ D ]  ← decrypt xong, đẩy vào _decryptedOctets
  └── decode được 1 gói (A,B) → "đã xử lý"

Code cũ: compact dựa trên "originalBlockLength" = 4 (chỉ của khối này!).

Lần đọc 2 (server gửi thêm rồi đóng):
[ C ][ D ][ E ][ F ][ G ]  ← E,F,G = PROTOCOL_PLAYERLOGOUT (gói 69)
         ↑
         C,D có thể bị coi nhầm / compact nhầm, hoặc E,F,G không được coi là "một gói đủ"

Kết quả: Gói logout (E,F,G) không bao giờ được decode, hoặc bị xóa khi compact.


3. Code cũ vs Code mới (ý tưởng)

  • Code cũ: Mỗi lần đọc socket = một khối riêng. Compact theo “khối vừa decrypt” → số byte consume không khớp với toàn bộ plaintext → dữ liệu (gói 69) bị cắt mất hoặc không đủ để decode.
  • Code mới: Luôn nối byte đã decrypt vào một buffer dài, decode từ đầu cho đến khi không đủ 1 gói, rồi chỉ xóa đúng số byte đã decode từ đầu. Phần còn lại giữ cho lần sau.

4. Code mới: “một hàng dài”, consume từ trái sang

_decryptedOctets (sau nhiều lần append):

[ A ][ B ][ C ][ D ][ E ][ F ][ G ][ H ] ...
  └─ gói 1 ─┘  └── gói 2 ──┘  └─ chưa đủ 1 gói ─┘
       │              │
       decode         decode
       consume 2      consume 4

Sau consume:
[ E ][ F ][ G ][ H ] ...   ← chỉ xóa 2+4 byte từ trái, giữ lại E,F,G,H...

Lần đọc tiếp (server gửi thêm rồi đóng):

[ E ][ F ][ G ][ H ][ I ][ J ]   ← I,J = phần còn lại của gói 69
  └──────── PROTOCOL_PLAYERLOGOUT ────────┘
       decode được → fire event → consume 6 byte

Gói 69 luôn nằm trong một hàng dài, không bị cắt theo “khối” → client nhận được PROTOCOL_PLAYERLOGOUT.


5. Bảng so sánh

Code cũ Code mới
Buffer plaintext Coi từng “khối” nhỏ, compact theo khối Một buffer dài, append liên tục
Consume Theo “original block length” (dễ sai) Đúng số byte đã decode (stream.Position)
Gói nằm giữa 2 lần đọc Dễ bị mất / không đủ để decode Giữ lại, lần sau decode tiếp
Kết quả với gói logout Gói 69 thường bị mất Gói 69 được decode và fire event

6. File thay đổi

  • NetworkManager.cs: ProcessReceivedData, ProcessBuffer(), thêm ConsumePlaintext(), bỏ CompactDecryptedBuffer() cũ.
  • GameSession.cs: Thêm log trong HandlePlayerLogout(): [GameSession] Received PROTOCOL_PLAYERLOGOUT (Result=..., Roleid=...).

7. Cách kiểm tra

Logout trong game và xác nhận log xuất hiện trước khi disconnect:

[GameSession] Received PROTOCOL_PLAYERLOGOUT (Result=..., Roleid=...)