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

MuxClient

MuxClient چند اتصال منطقی WaterWall را روی تعداد کمتری اتصال مشترک transport حمل می‌کند. به‌جای ساخت یک اتصال کامل تا سمت مقابل برای هر line ورودی، یک یا چند parent line به سمت next می‌سازد یا دوباره به کار می‌گیرد و رویدادهای هر child را در قالب فریم داخلی MUX می‌فرستد. در سمت مقابل، MuxServer این child lineهای مستقل را بازسازی می‌کند.

MuxClient خودش listener یا connector نیست؛ نود قبلی child lineها را می‌سازد و نود بعدی transport مشترک parent را فراهم می‌کند.

جایگاه رایج

TCP ساده:

client side: TcpListener -> MuxClient -> TcpConnector
server side: TcpListener -> MuxServer -> TcpConnector

با protocol stack بیرونی:

client side: TcpListener -> MuxClient -> HttpClient -> TlsClient -> TcpConnector
server side: TcpListener -> TlsServer -> HttpServer -> MuxServer -> TcpConnector

از MUX زمانی استفاده کنید که تعداد زیادی اتصال منطقی کوتاه یا متوسط باید روی تعداد کمتری اتصال بیرونی transport عبور کنند.

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

  • lineهای child معمولی را از نود قبلی می‌پذیرد.
  • parent transport lineها را به سمت next می‌سازد.
  • parent lineها را بر اساس حالت concurrency انتخاب‌شده دوباره به کار می‌گیرد.
  • به هر child یک connection id یا cid اختصاص می‌دهد.
  • payload و رویدادهای pause، resume و Finish هر child را در فریم MUX encode می‌کند.
  • فریم‌های برگشتی MuxServer را به child مناسب هدایت می‌کند.
  • اگر سمت نوشتن child محلی pause باشد، داده رسیده از parent را در صف نگه می‌دارد.
  • backpressure per-child را با FlowPause و FlowResume frame بازتاب می‌دهد.
  • parent lineهای exhausted و بی‌کار را پس از بسته شدن آخرین child می‌بندد.

MuxClient objectهای parent از نوع line_t را داخل خود می‌سازد و مسئول آزاد کردن آن‌هاست. Child lineهایی را که نود قبلی ساخته، آزاد نمی‌کند.

نمونه تنظیم‌ها

حالت timer:

{
"name": "mux-client",
"type": "MuxClient",
"settings": {
"mode": "timer",
"connection-duration-ms": 30000
},
"next": "outbound-transport"
}

حالت counter:

{
"name": "mux-client",
"type": "MuxClient",
"settings": {
"mode": "counter",
"connection-capacity": 128
},
"next": "outbound-transport"
}

حالت pool ثابت parentها:

{
"name": "mux-client",
"type": "MuxClient",
"settings": {
"mode": "fixed-connections-count",
"per-worker-connections-count": 2
},
"next": "outbound-transport"
}

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

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

فیلدنوعتوضیح
namestringنام یکتای نود در پیکربندی.
typestringباید دقیقاً "MuxClient" باشد.
settingsobjectاجباری. باید mode و تنظیم ویژه همان حالت را داشته باشد.
nextstringاجباری. نود بعدی parent transport lineها را حمل می‌کند.

فیلدهای اجباری داخل settings:

گزینهاجباری درتوضیح
modeهمیشهیکی از timer، counter یا fixed-connections-count.
connection-duration-msmode: "timer"مدت پذیرش child جدید روی هر parent، به میلی‌ثانیه. باید بزرگ‌تر از 60 باشد.
connection-capacitymode: "counter"حداکثر تعداد cid باز شده روی یک parent. باید بزرگ‌تر از 0 باشد.
per-worker-connections-countmode: "fixed-connections-count"تعداد parent lineهای ثابت برای هر worker. باید بزرگ‌تر از 0 باشد.

تنظیمات اختیاری

گزینهنوعپیش‌فرضتوضیح
child-buffer-limitinteger8388608بیشترین تعداد بایت صف‌شده برای یک child در حالت pause. باید بزرگ‌تر از 0 باشد. با رسیدن به سقف، child با فریم Close بسته می‌شود.
child-buffer-pause-toleranceinteger524288حدی که child یک FlowPause می‌فرستد و parent reads ممکن است pause شوند. باید 0 یا بزرگ‌تر باشد. مقادیر بالاتر از child-buffer-limit به همان حد cap می‌شوند.
log-main-line-statsbooleanfalseاگر فعال باشد، parent lineها هر 5000 ms آمار را در لاگ می‌نویسند.

آمار شامل worker id، وضعیت pause read/write والد، تعداد child، تعداد read-pause child و تعداد write-pause child است.

مدل parent و child

MuxClient دو نقش line دارد:

نقشمعنی
child lineاتصال منطقی WaterWall دریافتی از نود قبلی.
parent lineاتصال transport مشترک که MuxClient به سمت next می‌سازد.

هر child یک cid می‌گیرد. مقدار cid در محدوده همان parent line محلی است و در فریم‌های MUX به کار می‌رود تا MuxServer بتواند payload و رویدادهای کنترلی را به stream منطقی درست نسبت دهد.

در حالت timer و counter، هر worker یک parent فعال و قابل استفاده دارد. Childهای جدید تا exhausted شدن parent به آن متصل می‌شوند.

در حالت pool ثابت parentها، نخستین child روی یک worker باعث می‌شود MuxClient به تعداد per-worker-connections-count برای همان worker parent line بسازد. Childهای تازه به کم‌بارترین parent در pool آن worker می‌روند و در صورت مساوی بودن بار، round-robin تصمیم می‌گیرد. تا زمانی که slotهای pool زنده باشند، parent اضافه‌ای ساخته نمی‌شود.

رفتار modeها

modeparent چه زمانی child جدید نمی‌پذیردمناسب برای
timerوقتی عمر parent از connection-duration-ms بیشتر شودچرخش parent transport lineها در طول زمان.
counterوقتی cid به connection-capacity برسدcap ساده روی streamهای منطقی به‌ازای هر parent.
fixed-connections-countexhausted نمی‌شود بر اساس زمان یا تعداد childتعداد ثابت اتصال بیرونی per-worker.

Parentِ exhausted بی‌درنگ بسته نمی‌شود؛ فقط child تازه نمی‌پذیرد و childهای موجود تا پایان ادامه می‌دهند. اگر چنین parentی child نداشته باشد، MuxClient آن را می‌بندد و آزاد می‌کند.

همچنین یک hard limit مطلق برای cid وجود دارد: وقتی parent به 4294967295 برسد، exhausted می‌شود.

باز شدن Stream

کد فعلی یک child را در upstream Init attach می‌کند:

  1. یک parent line موجود یا تازه روی همان worker انتخاب می‌کند.
  2. state مربوط به MUX را برای child مقداردهی اولیه می‌کند.
  3. child را به parent link می‌کند.
  4. cid بعدی را اختصاص می‌دهد.
  5. downstream Est را به نود قبلی برای child گزارش می‌دهد.

فریم Open هنگام Init فرستاده نمی‌شود. با رسیدن نخستین payloadِ upstream، فریم‌های Open و Data در یک بافر ارسال می‌شوند. اگر child پیش از فرستادن payload finish شود، MuxClient ابتدا Open و سپس Close می‌فرستد تا peer یک توالی کامل open/close ببیند.

فرمت frame

MuxClient و MuxServer از header packed هشت‌بایتی مشترک استفاده می‌کنند:

فیلداندازهتوضیح
lengthuint16طول payload بعد از header، big-endian.
flagsuint8نوع frame.
_pad1uint8بایت padding داخلی.
ciduint32شناسه child stream، big-endian.

flagهای frame:

flagمعنی
0Open
1Close
2FlowPause
3FlowResume
4Data

پیاده‌سازی payloadهای بزرگ‌تر از 0xFFFF - 8 را رد می‌کند؛ بنابراین هر فریم داده MUX حداکثر 65527 بایت payload حمل می‌کند. MuxClient یک payload بزرگ child را میان چند فریم تقسیم نمی‌کند و داده ورودی باید از ابتدا در این سقف جا شود.

جهت و رفتار Callback

callback ورودیرفتار
upstream Init childیک parent انتخاب یا می‌سازد، child را link می‌کند، cid اختصاص می‌دهد، Est را به نود قبلی برای child گزارش می‌دهد.
upstream Payload childبرای اولین payload Open + Data prepend می‌کند، در غیر این صورت Data prepend می‌کند، سپس به parent به سمت next می‌فرستد.
upstream Pause / Resume childFlowPause می‌فرستد / داده queue شده را flush می‌کند و در صورت نیاز FlowResume می‌فرستد.
upstream Finish childClose می‌فرستد، یا Open + Close اگر هنوز payload stream را باز نکرده؛ در صورت نیاز parent exhausted بیکار را می‌بندد.
downstream Payload والدفریم‌های MUX از MuxServer را تجزیه می‌کند و Data، Close، FlowPause و FlowResume را به child مناسب می‌فرستد.
downstream Pause / Resume والدwrite pressure والد را به child نویسنده اخیر یا همه childها (اگر نویسنده اخیر نامشخص باشد) بازتاب می‌دهد.
downstream Finish والدparent را finishing علامت می‌زند، همه childها را به سمت نود قبلی flush/finish می‌کند، state parent و parent line تحت مالکیت خود را از بین می‌برد.
upstream Estغیرفعال؛ رسیدن به این callback fatal است.
downstream Initغیرفعال؛ رسیدن به این callback fatal است.

فریم‌های ناشناخته، فریم‌های مربوط به cid ناموجود و فریم Open ناخواسته از سمت سرور دور ریخته می‌شوند.

Backpressure و صف‌ها

MUX backpressure را per-child نگه می‌دارد:

  • اگر child محلی pause شود، MuxClient برای همان cid یک FlowPause می‌فرستد.
  • اگر FlowPause از peer برسد، خواندن از child محلی pause می‌شود.
  • اگر برای childی که pause است داده‌ای از parent برسد، داده روی همان child در صف می‌ماند.
  • وقتی صف یک child به child-buffer-pause-tolerance برسد، child یک FlowPause می‌فرستد و parent input ممکن است pause شود.
  • همین tolerance روی aggregate داده queue شده child روی parent line هم اعمال می‌شود.
  • وقتی داده queue شده پایین‌تر از min(512 KiB, child-buffer-limit) برسد، FlowResume و resume خواندن parent می‌توانند ارسال شوند.
  • اگر صف یک child به child-buffer-limit برسد، آن child بسته می‌شود.

به این ترتیب یک child کُند می‌تواند stream خود را متوقف کند، بی‌آنکه همه childهای دیگر روی همان parent فوراً متوقف شوند.

Buffer و Padding

MuxClient MUX headerها را به payload خروجی parent prepend می‌کند. متادیتای نود:

required_padding_left = 16
layer_group = kNodeLayerAnything

این 16 بایت لازم است، زیرا نخستین payloadِ child ممکن است دو header داشته باشد: یکی برای Open و دیگری برای Data. داده‌ها و فریم‌های کنترلی بعدی تنها یک header هشت‌بایتی دارند.

بایت‌های ورودی parent در یک stream خواندن جمع می‌شوند تا فریم کامل MUX آماده باشد. اگر این stream از 1 MiB بزرگ‌تر شود، parent و همه childهای متصل بسته می‌شوند.

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

  • MuxClient باید با MuxServer جفت شود.
  • mode در implementation فعلی اجباری است.
  • connection-duration-ms فقط برای timer mode اعمال می‌شود.
  • connection-capacity فقط برای counter mode اعمال می‌شود.
  • per-worker-connections-count فقط برای fixed parent pool mode اعمال می‌شود.
  • با چند worker، اندازه pool ثابت parentها نیز با تعداد workerها بالا می‌رود. برای مثال، per-worker-connections-count: 2 با چهار worker می‌تواند تا هشت parent transport line بسازد.
  • MuxClient یک تونل میانی است، نه endpoint عمومی.