تكرار السؤال: …
الإجابة المختصرة
استخدم message id أو sequence number وطبّق idempotency حسب contract واضح.
الشرح
الاتصال الموثوق على مستوى TCP لا يضمن أن التطبيق لا يفقد رسائل أو يستلمها مرتين: فبعد انقطاع وإعادة اتصال قد تُرسل الرسائل الأخيرة مرة أخرى، وقد تضيع رسائل صدرت أثناء الانقطاع، وقد تصل رسالتان بترتيب مختلف عبر مسارين. الحل عقد بين العميل والخادم: لكل رسالة معرّف فريد (id) أو رقم تسلسلي متزايد؛ يتجاهل العميل ما عالجه سابقًا (idempotency)، ويرسل عند إعادة الاتصال آخر رقم استلمه (cursor) ليعيد الخادم ما فاته (replay)، وتُرتَّب الرسائل بحسب الرقم لا بحسب وقت الوصول.
مثال بالكود
class OrderedFeed {
int _lastSeq = 0;
final _seenIds = <String>{};
void onMessage(Map<String, Object?> message) {
final id = message['id'] as String;
final seq = message['seq'] as int;
if (!_seenIds.add(id)) return; // duplicate after reconnect → ignore (idempotent)
if (seq <= _lastSeq) return; // older than what we already applied
if (seq > _lastSeq + 1) {
requestReplay(from: _lastSeq + 1); // gap detected → ask the server to resend
return;
}
apply(message);
_lastSeq = seq;
}
void onReconnected(WebSocketSink sink) {
sink.add(jsonEncode({'type': 'resume', 'after': _lastSeq})); // replay from our cursor
}
}أخطاء شائعة
- تطبيق الرسالة مرتين (مثل إضافة عنصر مكرر أو خصم رصيد) عند إعادة إرسالها بعد إعادة الاتصال.
- الاعتماد على وقت وصول الرسالة بدل رقم تسلسلي لترتيب الأحداث.
- عدم إعادة طلب ما فات أثناء الانقطاع فتبقى الواجهة ناقصة بصمت.