HalfDuplexServer
HalfDuplexServer بخش سرور HalfDuplexClient است. دو half-connection ورودی را
میگیرد، با intro داخلی ارسالی کلاینت آنها را به هم متصل میکند و یک line
منطقی عادی به سمت next میسازد.
این node زمانی استفاده میشود که سمت مقابل یک stream از نوع full-duplex را به دو اتصال upload و download جداگانه تقسیم کرده باشد.
جایگاه رایج
نمونه مستقیم داخل یک chain:
TesterClient -> HalfDuplexClient -> HalfDuplexServer -> TesterServer
نمونه روی TCP:
client side: TcpListener -> HalfDuplexClient -> TcpConnector
server side: TcpListener -> HalfDuplexServer -> TcpConnector
HalfDuplexServer باید در سمت مقابل مسیر transport نسبت به HalfDuplexClient
قرار بگیرد.
عملکرد
این node از دادهای که HalfDuplexClient در شروع هر half-connection قرار داده
متوجه میشود که آن اتصال upload است یا download، سپس اتصالهای متناظر را به هم
وصل میکند و یک اتصال منطقی جدید برای node بعدی میسازد.
این node socket باز نمیکند و packet line هم نمیسازد؛ فقط روی lineهای stream در وسط chain کار میکند.
نمونه تنظیم
{
"name": "halfduplex-server",
"type": "HalfDuplexServer",
"settings": {},
"next": "service-node"
}
فیلدهای لازم
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام دلخواه و یکتای node. |
type | string | باید دقیقاً "HalfDuplexServer" باشد. |
next | string | nodeای که line منطقی بازسازیشده را دریافت میکند. |
این node باید هم node قبلی داشته باشد و هم next. برای ابتدا یا انتهای chain
نیست.
تنظیمات
در پیادهسازی فعلی هیچ تنظیم اختصاصی اجباری یا اختیاری ندارد.
روش جفت کردن اتصالها
هر transport line ورودی ابتدا در حالت unknown است. سرور تا زمانی که هر ۱۷ بایت
داده داشته باشد buffer میکند و سپس intro ارسالی از HalfDuplexClient را میخواند.
بایت صفر فرمان دقیق نقش و ۱۶ بایت بعدی شناسه تازه ۱۲۸ بیتی pair هستند.
از این intro دو چیز مشخص میشود:
- این half متعلق به کدام اتصال منطقی است
- این half نقش upload دارد یا download
سرور دو map داخلی دارد:
| Map | کاربرد |
|---|---|
| upload-line map | نگهداری uploadهایی که پیش از download متناظر رسیدهاند. |
| download-line map | نگهداری downloadهایی که پیش از upload متناظر رسیدهاند. |
اگر half متناظر از قبل منتظر باشد، جفت بلافاصله ساخته میشود. در غیر این صورت، half فعلی داخل map مناسب ذخیره میشود تا طرف مقابل برسد.
ساخت Main Line
پس از آماده شدن هر دو half، HalfDuplexServer این مراحل را انجام میدهد:
- یک main line جدید روی worker مربوط به upload line میسازد
- main line را به هر دو half-connection وصل میکند
- node بعدی را با main line مقداردهی اولیه میکند
- intro کامل هفدهبایتی را از اولین payload آپلود حذف میکند
- اگر بعد از intro payload واقعی باقی مانده باشد، آن را به
nextمیفرستد
بعد از این مرحله، nodeهای downstream فقط یک اتصال عادی WaterWall میبینند و لازم نیست بدانند transport پشت آن دو اتصال بوده است.
Intro مربوط به download هیچ payloadی از کاربر ندارد و فقط برای پیدا کردن اتصال متناظر مصرف میشود. شناسه pair فقط metadata مسیریابی داخلی است و احراز هویت یا حفاظت transport را فراهم نمیکند؛ برای این ویژگیها باید security node مناسب config شود.
انتقال میان workerها
این node با wrapper داخلی pipe-tunnel ساخته میشود. اگر upload و download روی دو worker متفاوت برسند، half جدیدتر به worker مربوط به half قدیمیتر منتقل میشود و جفت کردن اتصالها همانجا ادامه پیدا میکند.
این نکته برای TCP مهم است، چون ممکن است دو اتصال upload و download بهصورت طبیعی روی workerهای متفاوت پذیرفته شوند.
رفتار جهتها
| جهت | رفتار |
|---|---|
| upstream payload روی upload line | پس از پیدا شدن اتصال متناظر، به main line و next فرستاده میشود. |
| upstream payload روی download line | مصرف میشود و bufferش برای استفاده مجدد برمیگردد؛ download مسیر کلاینت به سرور نیست. |
downstream payload از next | از طریق download line به سمت node قبلی برگردانده میشود. |
| upstream pause/resume روی download line | از طریق main line به next فرستاده میشود. |
downstream pause/resume از next | از طریق upload line به node قبلی فرستاده میشود. |
upload line داده کلاینت به service را حمل میکند و download line پاسخ service را به کلاینت برمیگرداند.
سرور برای هر line یک endpoint است: دو transport را قرض میگیرد و مالک main line بازسازیشده است. برای نگاشت payload، فشار و Finish میان آنها باید فعال بماند. ارسال دوبارهٔ دادههای شروع همچنان Pause سمت next را رعایت میکند؛ قانون عبور شفاف در nodeهای میانی معمولی این کنترل تولیدکننده را حذف نمیکند.
Buffer موقت
اگر upload قبل از download متناظر برسد، سرور payload آپلود را تا کامل شدن pair buffer میکند.
| مقدار | اندازه |
|---|---|
| حداکثر buffer برای upload منتظر | 131070 * max(1, ceil(L / 32768)) بایت |
| intro داخلی | 17 bytes (یک بایت نقش و یک شناسه تازه ۱۲۸ بیتی) |
required_padding_left | 0 bytes |
اگر buffer upload منتظر پیش از رسیدن download به این حد برسد، upload از map حذف و بسته میشود.
downloadها به این شکل buffer نمیشوند؛ معمولاً فقط بهعنوان ورودی منتظر در map نگهداری میشوند.
Lifecycle
در upstream Init برای هر half-line ورودی، state محلی مقداردهی میشود و همان
transport half به node قبلی برقرارشده اعلام میشود. main line منطقی تنها پس
از جفت شدن هر دو half ساخته میشود.
اگر یک half پیش از جفت شدن بسته شود، از map انتظار حذف و state آن پاک میشود. اگر upload منتظر bufferای داشته باشد، آن buffer برای استفاده مجدد برگردانده میشود.
اگر یکی از halfها پس از جفت شدن بسته شود، main line نیز finish و سپس نابود میشود؛ بستن half دیگر بهسمت node قبلی برای اجرا در صف قرار میگیرد.
اگر سمت downstream، main line بازسازیشده را ببندد، سرور download line را بیدرنگ finish میکند و بستن upload line را برای اجرا در صف میگذارد.
callbackهای UpStreamEst و DownStreamInit برای این node غیرفعال هستند.
نکتهها
- باید با
HalfDuplexClientاستفاده شود. - این node به intro داخلی ۱۷ بایتی ساختهشده توسط client وابسته است.
- اگر شناسه pair تکراری برای upload یا download منتظر برسد، اتصال تکراری بسته میشود.
- uploadهای منتظر تا زمان جفت شدن یا رسیدن به سقف تعیینشده میتوانند حافظه مصرف کنند.
- فعلاً تنظیم جداگانهای برای timeout یا اندازه buffer جفتسازی وجود ندارد.
متادیتای نود
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 | 0 bytes |
L مبنای مستقل بودجهٔ انتظار در pool مربوط به line است: ۶۴ KiB در S1/S2 و ۱ MiB در پروفایلهای بالاتر. این مبنا از اندازهٔ bufferهای عادی و سقف splice مستقل است. حد انتظار در S1/S2 برابر ۲۶۲۱۴۰ و در پروفایلهای بالاتر برابر ۴۱۹۴۲۴۰ بایت است. رسیدن به حد یا عبور از آن، هم برای ورودی نخست و هم افزودن بعدی، موجب بستن میشود؛ دادهٔ جفتشده مستقیم عبور میکند.
Splice و حافظهٔ مرحلهٔ آغازین
هر تحویلی که برای خواندن intro یا نگهداری آپلود منتظر استفاده شود، بهطور کامل به حافظهٔ معمولی منتقل میشود؛ این شامل بدنهٔ همراه intro هم هست. bufferهای نگهداریشده همیشه معمولی هستند. پس از جفت شدن، دقیقاً ۱۷ بایت حذف و بدنهٔ ذخیرهشده پیش از دادههای بعدی ارسال میشود. مسیر آماده در هر دو جهت، حتی بین workerها، wrapper معمولی یا splice را حفظ میکند. دادهٔ اضافی ورودی سمت دانلود بدون انتقال بدنه به حافظه دور ریخته میشود.
حد انتظار آپلود همچنان 131070 * max(1, ceil(basis / 32768)) و شامل intro است:
در S1/S2 رسیدن به ۲۶۲٬۱۴۰ بایت و در پروفایلهای بالاتر رسیدن به ۴٬۱۹۴٬۲۴۰ بایت
باعث بسته شدن میشود. همتای موجود پیش از بررسی این حد جفت میشود. این حد مربوط
به بایتهای منطقی در انتظار است، نه تحویل آماده یا مجموع حافظهٔ فیزیکی.
bufferهای جدید و bufferهای بزرگشده padding لازم برای ادامهٔ مسیر را حفظ میکنند.
هر دو node پرچم kNodeFlagSupportsSplice دارند. کلاینت برای intro آپلود به ۱۷ بایت
padding چپ نیاز دارد؛ padding موردنیاز سرور صفر است.
خواندن splice تنها با پشتیبانی پلتفرم و build، فعال بودن misc.splice و پشتیبانی
تمام nodeهای chainِ گسترشیافته، از جمله wrapper سرور و همسایهها، فعال میشود.
buffer معمولی در همهٔ وضعیتها پذیرفته میشود. تنظیم یا فیلد جدیدی به پروتکل اضافه
نشده است و ترجیح دائمی برای خواندن معمولی ثبت نمیشود.
ترتیب داده هنگام راهاندازی خط اصلی
پیش از فراخوانی Init node بعدی، مانع موقت ترتیب منتشر میشود. main line که مالک آن سرور است، بدنهٔ اولیهٔ معمولی را جدا از ورودیهای آپلود بعدی نگه میدارد. ورودی بازگشتی درون Init یا ارسال بدنهٔ اولیه نمیتواند از دادههای قدیمی جلو بزند. حتی بدنهٔ اولیهٔ خالی نیز مانع ترتیب Init را زودتر برنمیدارد.
ورودیهای بعدی که پشت این مانع نگه داشته میشوند، سه سقف مستقل دارند و رسیدن به خود سقف هم مجاز است: ۲ MiB بایت منطقی، ۲ MiB هزینهٔ تخصیص استاندارد buffer، و ۱٬۰۲۴ ورودی. ورودی خالی هم هزینهٔ تخصیص و یک ورودی مصرف میکند. ممکن است ظرفیت تخصیص پیش از حد بایتها به سقف برسد. رد پذیرش اتصال را میبندد و بایتهای TCP حذف نمیشوند. بدنهٔ اولیه جزو این بودجه نیست؛ پذیرش قبلی و محدودیت انتظار آپلود، از جمله ورودی بزرگ با همتای آماده، تغییر نکردهاند.
تمام دادهٔ نگهداریشده معمولی است. ورودی splice که وارد این صف شود بهطور کامل با padding لازم به حافظه منتقل میشود. ابتدا بدنهٔ اولیه ارسال میشود، سپس کل دنبالهٔ صف به ترتیب تحویل داده میشود. چند ورودی در یک buffer معمولی با اندازهٔ بررسیشده ترکیب میشوند؛ هنگام ترکیب هیچ callback بیرونی اجرا نمیشود. این حافظهٔ موقت تضمینی برای سقف مجموع حافظهٔ فیزیکی نیست.
ارسال دادهٔ نگهداریشده هنگام Pause متوقف میماند. Resume داخل Init فقط مجوز را ثبت میکند؛ Resume منبع پس از تحویل دادههای قدیمی و فقط وقتی node بعدی متوقف نیست ارسال میشود. مانع درست پیش از تحویل دنبالهٔ نهایی برداشته میشود. پس از آن مسیر آماده بدون صف است و wrapper معمولی یا splice را حفظ میکند. همهٔ مسیرهای بستن، داده و بودجهٔ متعلق به main line را پیش از اطلاع به همسایهها آزاد میکنند.