TcpOverUdpClient
TcpOverUdpClient یک byte stream شبیه TCP را از مسیری UDP عبور میدهد، بدون اینکه مرز packetها از بین برود. این نود در داخل از KCP استفاده میکند: payload را از نود قبلی میگیرد، آن را به segmentهای KCP تبدیل میکند و datagramهای حاصل را به نود بعدی میفرستد.
این نود معمولاً با TcpOverUdpServer در سمت مقابل استفاده میشود.
جایگاه رایج
سمت client:
TcpListener -> TcpOverUdpClient -> UdpConnector
TcpListener -> MuxClient -> TcpOverUdpClient -> UdpConnector
سمت server:
UdpListener -> TcpOverUdpServer -> TcpConnector
UdpListener -> TcpOverUdpServer -> MuxServer -> TcpConnector
نودی که بعد از TcpOverUdpClient قرار میگیرد باید مرز datagramها را حفظ کند. این نود در اصل برای مسیرهای انتقال 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 محاسبه میشود. |
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 را به نود بعدی میفرستد.
وقتی payload stream از نود قبلی میرسد:
- payload به chunkهایی با حداکثر اندازه KCP write MTU موثر تقسیم میشود.
- هر chunk یک flag یکبایتی داخلی prefix میگیرد.
- chunk فریمشده از طریق KCP ارسال میشود.
- datagramهای خروجی KCP در جهت upstream به نود بعدی فرستاده میشوند.
وقتی datagramها از نود بعدی برمیگردند:
- در صورت فعال بودن، FEC decoding اعمال میشود.
- KCP datagram را میگیرد.
- payloadهای کاملشده از KCP خوانده میشوند.
- flag یکبایتی داخلی حذف میشود.
- data frameها به شکل payload stream بازسازی میشوند و در جهت downstream به نود قبلی میروند.
flagهای داخلی
داخل payload KCP، TcpOverUdpClient از یک flag یکبایتی استفاده میکند:
| Flag | معنی |
|---|---|
0x00 | data frame. byteهای بعد از flag همان payload stream واقعی هستند. |
0xF0 | ping frame؛ نشانگر داخلی keepalive. |
0xFF | close frame؛ درخواست بستن line در peer. |
data frame خالی دور ریخته میشود.
MTU و padding
KCP MTU از GLOBAL_MTU_SIZE میآید. وقتی FEC غیرفعال است، همین مقدار بهعنوان MTU بیرونی KCP به کار میرود؛ با فعال شدن FEC نیز ۸ بایت header بیرونی آن از مقدار MTU کم میشود.
بودجه موثر payload برای write stream:
GLOBAL_MTU_SIZE - 20 - 8 - 24 - 1 - fec_overhead
عدد 1 اندازه همان flag داخلی TcpOverUdp است. نود مقدار required_padding_left = 1 را اعلام میکند تا بتواند پیش از تحویل داده به KCP این flag را به ابتدای آن اضافه کند.
رفتار 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
اگر حجم صف ارسال KCP از kcp-send-buffer-limit بالاتر برود، client ارسال downstream Pause به سمت stream قبلی را زمانبندی میکند. وقتی صف خروجی KCP دوباره از آستانه پایینتر بیاید، callback خروجی ارسال downstream Resume را زمانبندی میکند.
به این ترتیب، وقتی مسیر UDP شلوغ است یا peer با سرعت کافی داده را دریافت نمیکند، صف و مصرف حافظه بینهایت رشد نمیکنند.
رفتار 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 بسته خواهد شد.
متادیتای نود
| ویژگی | مقدار |
|---|---|
| Node flag | kNodeFlagNone |
| نود قبلی | مجاز |
| نود بعدی | مجاز، در استفاده معمول لازم |
| layer group | kNodeLayerAnything |
required_padding_left | 1 |
اشتباههای رایج
- بعد از
TcpOverUdpClientنود stream-only نگذارید؛ سمت بعدی باید packet boundary را حفظ کند. - FEC را فقط در یک سمت فعال نکنید.
- تعداد FEC shardها باید در هر دو سمت یکسان باشد.
- تصور نکنید KCP جلوی هر نوع packet loss را میگیرد؛ KCP داده را دوباره میفرستد و FEC اختیاری در برابر loss متوسط کمک میکند، اما loss شدید یا bursty همچنان مشکلساز است.
no-recv-timeout-msرا کمتر یا مساویping-interval-msنگذارید.