TcpOverUdpClient
TcpOverUdpClient یک byte stream شبیه TCP را از مسیری UDP عبور میدهد، بدون اینکه مرز packetها از بین برود. این node در داخل از KCP استفاده میکند: payload را از node قبلی میگیرد، آن را به segmentهای KCP تبدیل میکند و datagramهای حاصل را به node بعدی میفرستد.
این node معمولاً با TcpOverUdpServer در سمت مقابل استفاده میشود.
جایگاه رایج
سمت client:
TcpListener -> TcpOverUdpClient -> UdpConnector
TcpListener -> MuxClient -> TcpOverUdpClient -> UdpConnector
سمت server:
UdpListener -> TcpOverUdpServer -> TcpConnector
UdpListener -> TcpOverUdpServer -> MuxServer -> TcpConnector
nodeای که بعد از TcpOverUdpClient قرار میگیرد باید مرز datagramها را حفظ کند. این node در اصل برای مسیرهای انتقال UDP-style ساخته شده است.
نمونه پایه
{
"name": "tcp-over-udp-client",
"type": "TcpOverUdpClient",
"settings": {
"fec": false,
"kcp-send-window": 8192,
"kcp-recv-window": 8192,
"ping-interval-ms": 10000,
"no-recv-timeout-ms": 30000
},
"next": "udp-out"
}
connector سمت مقابل:
{
"name": "udp-out",
"type": "UdpConnector",
"settings": {
"address": "198.51.100.10",
"port": 9000
}
}
نمونه با FEC
{
"name": "tcp-over-udp-client",
"type": "TcpOverUdpClient",
"settings": {
"fec": true,
"fec-data-shards": 10,
"fec-parity-shards": 3
},
"next": "udp-out"
}
FEC را باید در هر دو سمت TcpOverUdpClient و TcpOverUdpServer با تعداد shard یکسان فعال کنید. اگر FEC فقط در یک سمت فعال باشد یا تعداد shardها با هم فرق کند، peer نمیتواند packet stream را درست decode کند.
فیلدهای ضروری
settings اختیاری است. اگر آن را ننویسید یا مقدارش object نباشد، مقادیر پیشفرض به کار میروند.
در حالت معمول next لازم است و باید به transport یا stackی اشاره کند که مرز datagramها را حفظ میکند.
تنظیمات اختیاری
| فیلد | پیشفرض | اعتبارسنجی | توضیح |
|---|---|---|---|
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. |
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، client ابتدا per-line state را مقداردهی میکند، یک session برای KCP میسازد و timer loop آن را راه میاندازد. اگر FEC فعال باشد state مربوط به آن را هم میسازد، یک ping frame اولیه در صف میگذارد و در پایان upstream Init را به node بعدی میفرستد.
وقتی payload stream از node قبلی میرسد:
- payload به chunkهایی با حداکثر اندازه KCP write MTU موثر تقسیم میشود.
- هر chunk یک flag یکبایتی داخلی prefix میگیرد.
- chunk فریمشده از طریق KCP ارسال میشود.
- datagramهای خروجی KCP در جهت upstream به node بعدی فرستاده میشوند.
وقتی datagramها از node بعدی برمیگردند:
- در صورت فعال بودن، FEC decoding اعمال میشود.
- KCP datagram را میگیرد.
- payloadهای کاملشده از KCP خوانده میشوند.
- flag یکبایتی داخلی حذف میشود.
- data frameها به شکل payload stream بازسازی میشوند و در جهت downstream به node قبلی میروند.
flagهای داخلی
داخل payload KCP، TcpOverUdpClient از یک flag یکبایتی استفاده میکند:
| 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ها بلافاصله فرستاده میشوند و پس از کامل شدن هر block، parity shardهای آن ارسال میشوند.
در receive، decoder:
- datagramهای KCP wrapشده با FEC را میپذیرد
- data shardهای معتبر را به KCP میدهد
- تلاش میکند datagramهای KCP گمشده را از parity shardها بازسازی کند
- packetهای FEC نامعتبر را دور میریزد
FEC مصرف پهنای باند را بالا میبرد. با تنظیم پیشفرض 10 data shard و 3 parity shard، هر block کامل نزدیک به ۳۰ درصد داده parity اضافه دارد؛ headerهای FEC نیز جداگانه به آن افزوده میشوند.
Backpressure
سیگنالهای ورودی Pause/Resume راهنما هستند و حتی در حالت تکراری عبور داده میشوند. این سیگنالها update، ارسال مجدد، ping یا خروجی آمادهٔ KCP را متوقف نمیکنند.
پس از پذیرش کامل ورودی و تحویل packetهای خروجی تولیدشده، client تعداد segmentهای صفشده و تأییدنشدهٔ KCP را بررسی میکند. بالاتر از kcp-send-buffer-limit، یک downstream Pause محلی به سمت stream قبلی میفرستد؛ پایینتر از آستانه، این توقف محلی را با Resume آزاد میکند. در مقدار برابر، وضعیت فعلی حفظ میشود. حتی update ناشی از ACK تنها نیز میتواند Resume بدهد و نیازی به تولید packet خروجی دیگری نیست. وضعیت محلی پیش از callback ثبت میشود و پس از Finish، سیگنالی به سمت بستهشده فرستاده نمیشود. task جداگانه یا صف مخصوص Resume لازم نیست.
این تنظیم آستانهٔ فشار راهنماست، نه سقف سخت حافظه یا پذیرش داده. پردازش KCP/FEC و timerهای session در تمام طول line لازماند؛ بنابراین node نمیتواند خودش را بهطور دائمی bypass کند.
رفتار Close و timeout
اگر سمت stream قبلی Finish شود، TcpOverUdpClient این مراحل را انجام میدهد:
- جهت downstream را بسته علامت میزند تا callbackهای re-entrant به سمتی که finish شده برنگردند
- یک close frame داخلی از طریق KCP میفرستد
- خروجی KCP را flush میکند
- per-line state محلی را از بین میبرد
- upstream
Finishرا به سمت packet میفرستد
اگر close frame از peer برسد، client ابتدا state محلی را از بین میبرد و سمت packet را finish میکند؛ سپس، اگر line هنوز زنده باشد، سمت stream را هم finish میکند.
اگر به مدت ping-interval-ms چیزی دریافت نشود، یک ping frame فرستاده میشود. اگر این بیخبری بیشتر از no-recv-timeout-ms طول بکشد، line بسته خواهد شد.
اشتباههای رایج
- بعد از
TcpOverUdpClientnode stream-only نگذارید؛ سمت بعدی باید packet boundary را حفظ کند. - FEC را فقط در یک سمت فعال نکنید.
- تعداد FEC shardها باید در هر دو سمت یکسان باشد.
- تصور نکنید KCP جلوی هر نوع packet loss را میگیرد؛ KCP داده را دوباره میفرستد و FEC اختیاری در برابر loss متوسط کمک میکند، اما loss شدید یا bursty همچنان مشکلساز است.
no-recv-timeout-msرا کمتر یا مساویping-interval-msنگذارید.
متادیتای 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 |