پرش به مطلب اصلی

KeepAliveClient

KeepAliveClient بخش کلاینت KeepAliveServer است. payload‌های upstream را در قالب فریم کوچک keepalive قرار می‌دهد و روی هر line زنده‌ای که این تونل دنبال می‌کند، به‌صورت دوره‌ای فریم ping داخلی می‌فرستد.

این node زمانی کاربرد دارد که مسیر WaterWall باید مرتب ترافیک تولید کند تا middleboxها، دستگاه‌های NAT یا فایروال‌های دارای timeout، اتصال را idle تشخیص ندهند. سمت مقابل باید KeepAliveServer باشد؛ وگرنه به‌جای payload اصلی، بایت‌های فریم‌شده را دریافت می‌کند.

KeepAliveClient یک تونل عادی و قابل ترکیب است. socket یا object‌ای از نوع line_t نمی‌سازد، line را آزاد نمی‌کند و پس از finish هیچ state پروتکلی را باقی نمی‌گذارد.

جایگاه رایج​

جفت مستقیم داخل یک process:

TesterClient -> KeepAliveClient -> KeepAliveServer -> TesterServer

چیدمان TCP روی دو سرور:

client side: TcpListener -> KeepAliveClient -> TcpConnector
server side: TcpListener -> KeepAliveServer -> TcpConnector

هر تونل میان کلاینت و سرور باید بایت‌های فریم keepalive را بدون تغییر عبور دهد.

این نود چه می‌کند؟​

  • با دریافت Init در upstream، state محلی line را مقداردهی اولیه می‌کند.
  • تعداد line‌های آماده‌شده را در state مربوط به worker مالک نگه می‌دارد.
  • در اولین Est مربوط به transport روی هر worker، یک idle table محلی می‌سازد.
  • برای ping دوره‌ای و مهلت پاسخ جاری از دو idle item جدا استفاده می‌کند.
  • ping خالی را با فاصلهٔ ping-interval زمان‌بندی می‌کند؛ sensitive mode پس از Est مربوط به transport آغاز می‌شود و منتظر پاسخ هر ping می‌ماند.
  • payload‌های upstream را در فریم عادی keepalive encode می‌کند و به next می‌فرستد.
  • payload‌های upstream بزرگ‌تر از 1 MiB را به چند frame عادی تقسیم می‌کند.
  • بایت‌های فریم‌شده downstream را تا کامل شدن فریم‌ها buffer می‌کند.
  • فریم‌های عادی downstream را decode و payload آن‌ها را به node قبلی می‌فرستد.
  • به فریم ping در downstream، با فرستادن فریم pong در upstream پاسخ می‌دهد.
  • در sensitive-mode، pong خالی downstream انتظار ping جاری را پایان می‌دهد؛ در غیر این صورت نادیده گرفته می‌شود.
  • فریم‌های عادی خالی و type‌های ناشناخته را دور می‌اندازد.
  • idle itemهای line را حذف می‌کند، state محلی آن را از بین می‌برد و Finish را در همان جهت منتشر می‌کند.

watchdog اختیاریِ پاسخ، در صورت نرسیدن pong یا دیر رسیدن آن اتصال را می‌بندد. این قابلیت به‌صورت پیش‌فرض غیرفعال است.

نمونه تنظیم​

{
"name": "ka-client",
"type": "KeepAliveClient",
"settings": {
"ping-interval": 30000,
"sensitive-mode": false,
"tolerance-ms": 90000
},
"next": "transport"
}

سمت server متناظر:

{
"name": "ka-server",
"type": "KeepAliveServer",
"settings": {},
"next": "service"
}

فیلدهای اجباری​

فیلدهای سطح اصلی:

فیلدنوعتوضیح
namestringنام یکتای node در config.
typestringباید دقیقاً "KeepAliveClient" باشد.
settingsobjectاختیاری. در صورت حذف، interval‌های پیش‌فرض استفاده می‌شوند و watchdog پاسخ غیرفعال است.
nextstringدر استفاده عادی اجباری است. node بعدی داده فریم‌شده upstream را می‌گیرد.

KeepAliveClient باید node قبلی داشته باشد و در مسیر منطقی به KeepAliveServer متناظر برسد.

تنظیمات​

گزینهنوعپیش‌فرضتوضیح
ping-intervalinteger30000فاصله ping دوره‌ای به میلی‌ثانیه. باید حداقل 1 باشد؛ در صورت کمتر بودن startup شکست می‌خورد.
sensitive-modebooleanfalseتنها یک ping منتظر پاسخ روی هر line نگه می‌دارد و در صورت نرسیدن pong یا دیر رسیدن آن اتصال را می‌بندد.
tolerance-msinteger90000مهلت پاسخ به میلی‌ثانیه، با احتساب Pause محلی. باید در بازهٔ 1..2147483647 باشد و در sensitive mode استفاده می‌شود.

از interval‌های بسیار کوتاه استفاده نکنید، مگر اینکه عمداً ترافیک keepalive زیادی می‌خواهید. وقتی sensitive mode غیرفعال است، کلاینت در هر interval روی هر line زنده یک ping می‌فرستد؛ Pause مانع ارسال ping موعدرسیده نمی‌شود.

در هر دو حالت، اولین ping یک ping-interval پس از Est مربوط به transport موعد دارد. Est تکراری زمان‌بندی را عقب نمی‌اندازد و انتظار برای ping مانع دادهٔ برنامه نمی‌شود. ping موعدرسیده منتظر Resume نمی‌ماند. callback فعال encoder می‌تواند ارسال را تا بررسی بعدی عقب بیندازد؛ pingهای ازدست‌رفته به‌صورت یک‌جا ارسال نمی‌شوند. مهلت پاسخ هنگام تحویل ping به node بعدی آغاز می‌شود و زمان انتظار در صف آن node را نیز در بر می‌گیرد.

Watchdog پاسخ​

در sensitive mode، اولین ping یک ping-interval پس از Est مربوط به transport در downstream ارسال می‌شود. تنها یک ping روی هر line می‌تواند منتظر پاسخ باشد. pong خالی روی همان line انتظار را پایان می‌دهد. فریم داده، ping سمت مقابل، نوع ناشناخته و pong دارای payload پاسخ معتبر نیستند؛ pong بدون انتظار جاری نادیده گرفته می‌شود.

نرسیدن pong یا دیر رسیدن آن، اتصال از نوع borrowed را از مسیر Finish و از طریق owner می‌بندد. KeepAliveClient اتصال را دوباره نمی‌سازد. deadline از ساعت monotonic event loop worker مالک استفاده می‌کند. item جداگانهٔ deadline با pong به‌موقع حذف می‌شود و item مربوط به ping هرگز این مهلت را تمدید نمی‌کند. timeout در اولین بررسی idle table در یا پس از deadline، یا هنگام decode شدن pong دیررس اعمال می‌شود. deadline جدید و کوتاه ممکن است حدود یک ثانیه دیرتر بررسی شود؛ دقت بیدارسازی زیر یک ثانیه تضمین نمی‌شود.

Pause و Resume در هر دو جهت، حتی در صورت تکرار، به‌صورت شفاف منتقل می‌شوند. Ping موعدرسیده و Pong آماده منتظر Resume نمی‌مانند. parser ورودی Ping بازگشتی را با همان محدودیت‌های ذخیره‌سازی، پشت پاسخ فعال نگه می‌دارد؛ صف مستقلی برای Pong وجود ندارد. مهلت پاسخ جاری ادامه دارد و Pong خالیِ به‌موقع هنگام Pause نیز آن را پایان می‌دهد. Finish پردازش را متوقف می‌کند، ورودی نگه‌داشته‌شده را آزاد و هر دو idle item را حذف می‌کند.

مهلت را متناسب با توان انتقال و تأخیر مسیر انتخاب کنید. pong و فریم‌های عادی در همان stream مرتب قرار دارند؛ زمان Pause، صف، انتقال و بدنهٔ ۱ MiB می‌توانند پاسخ را عقب بیندازند. KeepAliveServer همین pingها را پاسخ می‌دهد.

فرمت Frame​

هر frame با یک prefix پنج‌بایتی شروع می‌شود:

Bytesفیلدتوضیح
0..3body lengthعدد صحیح unsigned و 32-bit با ترتیب big-endian. این طول شامل بایت type فریم به‌علاوه بایت‌های payload است.
4frame kind1 payload عادی، 2 ping، یا 3 pong.

نوع‌های frame:

KindمعنیBody payload
1payload عادیبایت‌های payload اصلی WaterWall.
2pingدر ترافیک keepalive عادی خالی است.
3pongپاسخ خالی به یک ping frame.

طول body شامل بایت kind هم می‌شود. هر فریم حداکثر 1 MiB (1,048,576 بایت) payload دارد و حداکثر طول بدنه 1,048,577 است. payload‌های upstream بزرگ‌تر به چند فریم تقسیم می‌شوند. هر دو peer از همین prefix پنج‌بایتی استفاده می‌کنند.

رفتار جهت‌ها​

Incoming callbackرفتار
upstream Initbuffer خواندن و state محلی را مقداردهی اولیه می‌کند، line را در worker مالک ثبت می‌کند و سپس Init را به next می‌فرستد.
upstream Payloadpayload را در یک یا چند فریم عادی encode و به next ارسال می‌کند. buffer payload‌های خالی برای استفاده مجدد برمی‌گردد.
upstream Estبه next فرستاده می‌شود.
upstream Pause / Resumeهر اعلان، حتی تکراری، به next فرستاده می‌شود؛ مهلت پاسخ جاری ادامه دارد.
upstream Finishهر دو idle item را حذف و state محلی را از بین می‌برد، سپس Finish را به next می‌فرستد.
downstream Payloadبایت‌ها را ورودی فریم‌شده KeepAliveServer در نظر می‌گیرد و payload‌های عادی decodeشده را به node قبلی می‌فرستد.
downstream Estآمادگی انتقال برای watchdog ثبت می‌شود، سپس فوری به node قبلی فرستاده می‌شود.
downstream Pause / Resumeهر اعلان، حتی تکراری، به node قبلی فرستاده می‌شود؛ خروجی آمادهٔ Ping/Pong و مهلت پاسخ ادامه دارند.
downstream Finishهر دو idle item را حذف و state محلی را از بین می‌برد، سپس Finish را به node قبلی می‌فرستد.
downstream Initغیرفعال؛ رسیدن به این callback به‌عنوان chain flow نامعتبر fatal در نظر گرفته می‌شود.

پردازش ورودی Framed​

بایت‌های downstream در یک splice_stream_t جمع می‌شوند؛ بنابراین ممکن است یک فریم در چند callbackِ payload تکه‌تکه برسد یا چند فریم در یک callback دریافت شوند.

رفتار decoder محافظه‌کارانه است:

  • وقتی prefix یا body کامل نرسیده، منتظر بایت‌های بعدی می‌ماند.
  • اگر طول body فریم خارج از بازهٔ 1..1,048,577 باشد، line از طریق owner بسته می‌شود
  • فریم‌های عادی خالی دور ریخته می‌شوند.
  • type‌های ناشناخته فریم دور ریخته می‌شوند.
  • فریم‌های کامل ابتدا تخلیه می‌شوند؛ اگر پس از compaction هزینهٔ تخصیص همچنان بیش از سقف باشد، line بسته می‌شود

یادداشت‌های Buffer و Padding​

KeepAliveClient هنگام encode کردن، prefix پنج‌بایتی frame را prepend می‌کند، بنابراین metadata این node چنین است:

required_padding_left = 5
layer_group = kNodeLayer4
layer_group_next_node = kNodeLayer4
layer_group_prev_node = kNodeLayer4

caller باید padding کافی در سمت چپ برای prefix فراهم کند؛ Debug این پیش‌شرط را assert می‌کند. محاسبه padding در chain این فضای اعلام‌شده را رزرو می‌کند.

نکته‌های عملی​

  • این node را با KeepAliveServer جفت کنید؛ به‌تنهایی مفید نیست.
  • فریم‌های ping و pong داخلی جفت KeepAlive هستند و به‌عنوان payload به node‌های اطراف داده نمی‌شوند.
  • این node یک مسیر را فعال نگه می‌دارد؛ health-check protocol خارجی نیست.
  • این node بایت‌های payload را رمزنگاری، احراز هویت، فشرده یا بازنویسی نمی‌کند.
  • برای استفادهٔ مطمئن، transport باید datagram یا stream و بایت‌های فریم‌شده را بین کلاینت و سرور بدون تغییر نگه دارد.

متادیتای نود​

metadata برگرفته از کد منبع:

ویژگیمقدار
node flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left5 bytes

پشتیبانی از splice​

KeepAliveClient پرچم kNodeFlagSupportsSplice را دارد. framing در تمام طول اتصال ادامه دارد. decoder از cache پنج‌بایتی splice_stream_t استفاده می‌کند و encoder فقط header پنج‌بایتی را در padding واقعی سمت چپ می‌نویسد. بدنهٔ payload می‌تواند در pipe باقی بماند. payload بزرگ‌تر از ۱ MiB با عملیات range متناسب با نوع buffer تقسیم می‌شود. شکست تخصیص pipe یا فشار ظرفیت می‌تواند fallback کامل به buffer عادی ایجاد کند. فریم‌های کنترلی buffer عادی دارند.

ورودی re-entrant encoder پشت suffix فعال می‌ماند و مجموع آن‌ها به ۸ MiB بایت منطقی و ۱٬۰۲۴ buffer، با احتساب suffix فعال، محدود است. decoder ورودی nested را به ترتیب پردازش می‌کند؛ بایت‌های nested سقف ۸ MiB و هزینهٔ تخصیص باقی‌مانده سقف ۱۶ MiB دارند. پیش از رد هزینهٔ اضافی، ابتدا compaction به buffer عادی، در صورت کاهش هزینه، امتحان می‌شود. فریم‌های کامل پیش از بررسی هزینهٔ نگهداری تخلیه می‌شوند. اعتبارسنجی طول فریم تضمین می‌کند که remainder ناقص، با احتساب header، کوچک‌تر از ۱ MiB + ۵ بایت باشد. Pause فوری منتقل می‌شود و پردازش batch همگامِ پذیرفته‌شده را قطع نمی‌کند.

طول بدنهٔ صفر یا بیش از ۱ MiB + ۱، شکست admission یا سرریز ذخیره‌سازی، line از نوع borrowed را از طریق owner می‌بندد. Finish، فریم ناقص، suffix فعال encoder و صف ورودی re-entrant را آزاد می‌کند؛ این node خودش line_t را destroy نمی‌کند.

Ping موعدرسیده و Pong آماده هنگام Pause نیز ارسال می‌شوند. framing و watchdog تا پایان اتصال لازم‌اند؛ بنابراین این node فعال می‌ماند و bypass دائمی نمی‌شود.