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"
}
فیلدهای اجباری
فیلدهای سطح اصلی:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام یکتای node در config. |
type | string | باید دقیقاً "KeepAliveClient" باشد. |
settings | object | اختیاری. در صورت حذف، intervalهای پیشفرض استفاده میشوند و watchdog پاسخ غیرفعال است. |
next | string | در استفاده عادی اجباری است. node بعدی داده فریمشده upstream را میگیرد. |
KeepAliveClient باید node قبلی داشته باشد و در مسیر منطقی به KeepAliveServer متناظر برسد.
تنظیمات
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
ping-interval | integer | 30000 | فاصله ping دورهای به میلیثانیه. باید حداقل 1 باشد؛ در صورت کمتر بودن startup شکست میخورد. |
sensitive-mode | boolean | false | تنها یک ping منتظر پاسخ روی هر line نگه میدارد و در صورت نرسیدن pong یا دیر رسیدن آن اتصال را میبندد. |
tolerance-ms | integer | 90000 | مهلت پاسخ به میلیثانیه، با احتساب 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..3 | body length | عدد صحیح unsigned و 32-bit با ترتیب big-endian. این طول شامل بایت type فریم بهعلاوه بایتهای payload است. |
4 | frame kind | 1 payload عادی، 2 ping، یا 3 pong. |
نوعهای frame:
| Kind | معنی | Body payload |
|---|---|---|
1 | payload عادی | بایتهای payload اصلی WaterWall. |
2 | ping | در ترافیک keepalive عادی خالی است. |
3 | pong | پاسخ خالی به یک ping frame. |
طول body شامل بایت kind هم میشود. هر فریم حداکثر 1 MiB (1,048,576 بایت) payload دارد و حداکثر طول بدنه 1,048,577 است. payloadهای upstream بزرگتر به چند فریم تقسیم میشوند. هر دو peer از همین prefix پنجبایتی استفاده میکنند.
رفتار جهتها
| Incoming callback | رفتار |
|---|---|
upstream Init | buffer خواندن و state محلی را مقداردهی اولیه میکند، line را در worker مالک ثبت میکند و سپس Init را به next میفرستد. |
upstream Payload | payload را در یک یا چند فریم عادی 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 flags | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 5 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 دائمی نمیشود.