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

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‌ها را حفظ می‌کند.

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

فیلدپیش‌فرضاعتبارسنجیتوضیح
fecfalsebooleanفعال‌سازی Reed-Solomon forward error correction روی datagram‌های KCP.
fec-data-shards10مثبت، با مجموع shard‌ها حداکثر 255تعداد data shard در هر FEC block. فقط وقتی fec فعال است استفاده می‌شود.
fec-parity-shards3مثبت، با مجموع shard‌ها حداکثر 255تعداد parity shard در هر FEC block. فقط وقتی fec فعال است استفاده می‌شود.
kcp-nodelaytruebooleanفعال‌سازی KCP nodelay mode.
kcp-interval-ms1010..5000فاصله زمانی بین update‌های KCP.
kcp-resend2>= 0آستانه fast-resend در KCP.
kcp-no-congestion-controltruebooleanغیرفعال کردن congestion control در KCP.
kcp-send-window8192>= 1send window بر حسب segment.
kcp-recv-window8192>= 1receive window بر حسب segment.
kcp-initial-cwndنصف kcp-send-window1..kcp-send-windowcongestion window اولیه KCP.
kcp-rx-minrto-ms30>= 1حداقل retransmission timeout در KCP.
kcp-send-buffer-limit0>= 0آستانه backpressure. با مقدار 0، حد از مجموع local send window، remote window و 10 محاسبه و به 2147483647 محدود می‌شود.
ping-interval-ms10000>= 1اگر تا این مدت داده‌ای دریافت نشود، یک ping frame داخلی فرستاده می‌شود.
no-recv-timeout-ms30000بزرگ‌تر از 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 قبلی می‌رسد:

  1. payload به chunk‌هایی با حداکثر اندازه KCP write MTU موثر تقسیم می‌شود.
  2. هر chunk یک flag یک‌بایتی داخلی prefix می‌گیرد.
  3. chunk فریم‌شده از طریق KCP ارسال می‌شود.
  4. datagram‌های خروجی KCP در جهت upstream به node بعدی فرستاده می‌شوند.

وقتی datagram‌ها از node بعدی برمی‌گردند:

  1. در صورت فعال بودن، FEC decoding اعمال می‌شود.
  2. KCP datagram را می‌گیرد.
  3. payload‌های کامل‌شده از KCP خوانده می‌شوند.
  4. flag یک‌بایتی داخلی حذف می‌شود.
  5. data frame‌ها به شکل payload stream بازسازی می‌شوند و در جهت downstream به node قبلی می‌روند.

flag‌های داخلی​

داخل payload KCP، TcpOverUdpClient از یک flag یک‌بایتی استفاده می‌کند:

Flagمعنی
0x00data frame. بایت‌های بعد از flag همان payload stream واقعی هستند.
0xF0ping frame؛ نشانگر داخلی keepalive.
0xFFclose 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 این مراحل را انجام می‌دهد:

  1. جهت downstream را بسته علامت می‌زند تا callback‌های re-entrant به سمتی که finish شده برنگردند
  2. یک close frame داخلی از طریق KCP می‌فرستد
  3. خروجی KCP را flush می‌کند
  4. per-line state محلی را از بین می‌برد
  5. upstream Finish را به سمت packet می‌فرستد

اگر close frame از peer برسد، client ابتدا state محلی را از بین می‌برد و سمت packet را finish می‌کند؛ سپس، اگر line هنوز زنده باشد، سمت stream را هم finish می‌کند.

اگر به مدت ping-interval-ms چیزی دریافت نشود، یک ping frame فرستاده می‌شود. اگر این بی‌خبری بیشتر از no-recv-timeout-ms طول بکشد، line بسته خواهد شد.

اشتباه‌های رایج​

  • بعد از TcpOverUdpClient node 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 flagskNodeFlagBlocksSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left1 byte