TcpOverUdpServer
TcpOverUdpServer peer سمت server برای TcpOverUdpClient است. datagramهای KCP را بدون از بین بردن مرز packetها از node قبلی میگیرد، byte stream اصلی را بازسازی میکند و آن را به node بعدی میفرستد. پاسخ node بعدی نیز دوباره به datagramهای KCP تبدیل میشود و در جهت downstream به سمت packet قبلی برمیگردد.
این node معمولاً با TcpOverUdpClient در سمت مقابل استفاده میشود.
جایگاه رایج
سمت server:
UdpListener -> TcpOverUdpServer -> TcpConnector
UdpListener -> TcpOverUdpServer -> MuxServer -> TcpConnector
سمت client متناظر:
TcpListener -> TcpOverUdpClient -> UdpConnector
TcpListener -> MuxClient -> TcpOverUdpClient -> UdpConnector
node قبلی TcpOverUdpServer باید مرز datagramها را حفظ کند؛ node بعدی با stream سروکار دارد.
نمونه پایه
{
"name": "tcp-over-udp-server",
"type": "TcpOverUdpServer",
"settings": {
"fec": false,
"kcp-send-window": 8192,
"kcp-recv-window": 8192,
"ping-interval-ms": 10000,
"no-recv-timeout-ms": 30000
},
"next": "service-out"
}
connector سرویس در ادامه مسیر:
{
"name": "service-out",
"type": "TcpConnector",
"settings": {
"address": "127.0.0.1",
"port": 8080
}
}
نمونه با FEC
{
"name": "tcp-over-udp-server",
"type": "TcpOverUdpServer",
"settings": {
"fec": true,
"fec-data-shards": 10,
"fec-parity-shards": 3
},
"next": "service-out"
}
تنظیمات FEC باید دقیقاً با سمت TcpOverUdpClient یکسان باشد.
فیلدهای ضروری
settings اختیاری است. اگر آن را ننویسید یا مقدارش object نباشد، مقادیر پیشفرض به کار میروند.
در حالت معمول next لازم است و باید به مسیر stream-side سرویس اشاره کند؛ همان مسیری که payload بازسازیشده را دریافت خواهد کرد.
تنظیمات اختیاری
نام settingها و مقدارهای پیشفرض TcpOverUdpServer با TcpOverUdpClient یکسان است.
| فیلد | پیشفرض | اعتبارسنجی | توضیح |
|---|---|---|---|
fec | false | boolean | فعالسازی Reed-Solomon forward error correction روی datagramهای KCP. |
fec-data-shards | 10 | مثبت، با مجموع shardها حداکثر 255 | تعداد data shard در هر FEC block. فقط وقتی fec فعال است استفاده میشود. |
fec-parity-shards | 3 | مثبت، با مجموع shardها حداکثر 255 | تعداد parity shard در هر FEC block. فقط وقتی fec فعال است استفاده میشود. |
kcp-nodelay | true | boolean | فعالسازی KCP nodelay mode. |
kcp-interval-ms | 10 | 10..5000 | فاصله زمانی بین updateهای KCP. |
kcp-resend | 2 | >= 0 | آستانه fast-resend در KCP. |
kcp-no-congestion-control | true | boolean | غیرفعال کردن congestion control در KCP. |
kcp-send-window | 8192 | >= 1 | send window بر حسب segment. |
kcp-recv-window | 8192 | >= 1 | receive window بر حسب segment. |
kcp-initial-cwnd | نصف kcp-send-window | 1..kcp-send-window | congestion window اولیه KCP. |
kcp-rx-minrto-ms | 30 | >= 1 | حداقل retransmission timeout. |
kcp-send-buffer-limit | 0 | >= 0 | آستانه backpressure. با مقدار 0، حد از مجموع local send window، remote window و 10 محاسبه و به 2147483647 محدود میشود. |
ping-interval-ms | 10000 | >= 1 | اگر تا این مدت دادهای دریافت نشود، یک ping frame داخلی فرستاده میشود. |
no-recv-timeout-ms | 30000 | بزرگتر از ping-interval-ms | اگر تا این مدت دادهای دریافت نشود، line بسته میشود. |
مدل KCP transport
هنگام دریافت upstream Init، server ابتدا per-line state را مقداردهی میکند، یک session برای KCP میسازد و timer loop آن را راه میاندازد. اگر FEC فعال باشد state مربوط به آن را هم میسازد، یک ping frame اولیه در صف میگذارد و در پایان upstream Init را به node stream-side بعدی میفرستد.
وقتی payload packet از node قبلی میرسد:
- در صورت فعال بودن، FEC decoding اعمال میشود.
- KCP datagram را میگیرد.
- payloadهای کاملشده از KCP خوانده میشوند.
- flag یکبایتی داخلی حذف میشود.
- data frameها به شکل payload stream بازسازی میشوند و در جهت upstream به node بعدی میروند.
وقتی payload stream از node بعدی برمیگردد:
- payload به chunkهایی با حداکثر اندازه KCP write MTU موثر تقسیم میشود.
- هر chunk data flag یکبایتی prefix میگیرد.
- chunk فریمشده از طریق KCP ارسال میشود.
- datagramهای خروجی KCP در جهت downstream به سمت packet قبلی فرستاده میشوند.
flagهای داخلی
داخل payload KCP، TcpOverUdpServer از همان flagهای frame یکبایتی سمت client استفاده میکند:
| Flag | معنی |
|---|---|
0x00 | data frame. بایتهای بعد از flag همان payload stream واقعی هستند. |
0xF0 | ping frame؛ نشانگر داخلی keepalive. |
0xFF | close frame؛ درخواست بستن line در peer. |
data frame خالی دور ریخته میشود.
MTU و padding
گزینهٔ اختیاری و صحیح mtu هنگام ساخت از misc.mtu هسته مقدار میگیرد؛ پیشفرض
هسته 1500 است. بازهٔ معتبر 78..65535 بدون FEC و 86..65535 با FEC است.
این مقدار بودجهٔ بسته برحسب بایت با فرض IPv4 است. مقدار صریح یا ارثرسیدهٔ نامعتبر
ساخت node را رد میکند؛ با override معتبر میتوان پیشفرض نامناسب هسته را جایگزین
کرد. null، نوع نادرست، عدد اعشاری، صفر، عدد منفی و مقدار خارج از بازه خطا هستند.
هر نمونه مقدار معتبر خود را مستقل و ثابت نگه میدارد.
با F = 0 بدون FEC و F = 8 با FEC:
KCP MTU = min(mtu - 20 - 8 - F, 64000)
application chunk = KCP MTU - 24 - 1
سهم ثابت IPv4 برابر 20 بایت و UDP برابر 8 بایت است. سقف دهدهی 64000 شامل کل
بستهٔ KCP و هدر 24 بایتی آن است. پرچم یکبایتی داخل payload قرار دارد و node
required_padding_left = 1 اعلام میکند. همین MTU خروجی داده و ACK/control
تجمیعشده را محدود میکند؛ FEC حداکثر 8 بایت، شامل بستههای parity، اضافه میکند.
برای mtu: 1500، MTU مؤثر KCP و chunk برنامه بدون FEC برابر 1472/1447 و
با FEC برابر 1464/1439 هستند. بودجهٔ 64028 بدون FEC یا 64036 با FEC به سقف
میرسد. مقادیر معتبر بزرگتر، از جمله 65535، پذیرفته میشوند و MTU مؤثر 64000
و chunk برابر 63975 دارند؛ خود تنظیم به 64000 کاهش نمییابد.
این تنظیم کشف یا مذاکرهٔ MTU مسیر نیست. هدر پایهٔ IPv6 بیست بایت بیشتر نیاز دارد؛ بودجهٔ 1500 میتواند بستهٔ IPv6 با طول 1520 بسازد. گزینههای IPv4، هدرهای توسعهٔ IPv6 و پوششهای بعدی به حاشیهٔ اضافی نیاز دارند. تشخیص خودکار خانواده انجام نمیشود.
رفتار FEC
با fec: true، datagramهای خروجی KCP در packetهای Reed-Solomon FEC قرار میگیرند. data shardها بلافاصله و parity shardها پس از کامل شدن هر block فرستاده میشوند.
در receive، decoder:
- datagramهای KCP wrapشده با FEC را میپذیرد
- data shardهای معتبر را به KCP میدهد
- تلاش میکند datagramهای KCP گمشده را از parity shardها بازسازی کند
- packetهای FEC نامعتبر را دور میریزد
FEC در دو سمت این pair رفتاری متقارن دارد. آن را در هر دو peer فعال کنید و تعداد shardها را یکسان بگذارید.
Backpressure
سیگنالهای ورودی Pause/Resume راهنما هستند و حتی در حالت تکراری عبور داده میشوند. این سیگنالها update، ارسال مجدد، ping یا خروجی آمادهٔ KCP را متوقف نمیکنند.
پس از پذیرش کامل ورودی و تحویل packetهای خروجی تولیدشده، server تعداد segmentهای صفشده و تأییدنشدهٔ KCP را بررسی میکند. بالاتر از kcp-send-buffer-limit، یک upstream Pause محلی به سمت stream بعدی میفرستد؛ پایینتر از آستانه، این توقف محلی را با Resume آزاد میکند. در مقدار برابر، وضعیت فعلی حفظ میشود. حتی update ناشی از ACK تنها نیز میتواند Resume بدهد و نیازی به تولید packet خروجی دیگری نیست. وضعیت محلی پیش از callback ثبت میشود و پس از Finish، سیگنالی به سمت بستهشده فرستاده نمیشود. task جداگانه یا صف مخصوص Resume لازم نیست.
این تنظیم آستانهٔ فشار راهنماست، نه سقف سخت حافظه یا پذیرش داده. پردازش KCP/FEC و timerهای session در تمام طول line لازماند؛ بنابراین node نمیتواند خودش را بهطور دائمی bypass کند.
رفتار Close و timeout
اگر سمت stream بعدی Finish شود، TcpOverUdpServer این مراحل را انجام میدهد:
- جهت upstream را بسته علامت میزند تا callbackهای re-entrant به سمتی که finish شده برنگردند
- یک close frame داخلی از طریق KCP میفرستد
- خروجی KCP را flush میکند
- per-line state محلی را از بین میبرد
- downstream
Finishرا به سمت packet میفرستد
اگر close frame از peer برسد، server ابتدا state محلی را از بین میبرد و سمت stream را finish میکند؛ سپس، اگر line هنوز زنده باشد، سمت packet را هم finish میکند.
اگر به مدت ping-interval-ms چیزی دریافت نشود، یک ping frame فرستاده میشود. اگر این بیخبری بیشتر از no-recv-timeout-ms طول بکشد، line بسته خواهد شد.
اشتباههای رایج
TcpOverUdpServerرا از node stream-only قبلی تغذیه نکنید. سمت قبلی باید packet boundary را حفظ کند.- بدون
TcpOverUdpClientمتناظر استفاده نکنید. - FEC را فقط در یک سمت فعال نکنید.
- تعداد FEC shardها باید در هر دو سمت یکسان باشد.
TcpOverUdpServerرا chain end در نظر نگیرید؛ payload stream بازسازیشده را بهnextمیفرستد.
متادیتای node
متادیتای برگرفته از کد منبع:
این node با kNodeFlagBlocksSplice استفاده از splice را برای کل chain صریحاً غیرفعال میکند. این پرچم حتی در صورت وجود همزمان kNodeFlagSupportsSplice اولویت دارد.
| ویژگی | مقدار |
|---|---|
| node flags | kNodeFlagBlocksSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 1 byte |