4.5 KiB
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êmConsumePlaintext(), bỏCompactDecryptedBuffer()cũ.GameSession.cs: Thêm log trongHandlePlayerLogout():[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=...)