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

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

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

فیلدپیش‌فرضاعتبارسنجیتوضیح
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 محاسبه می‌شود.
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 را به نود بعدی می‌فرستد.

وقتی payload stream از نود قبلی می‌رسد:

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

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

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

flagهای داخلی

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

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

  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 بسته خواهد شد.

متادیتای نود

ویژگیمقدار
Node flagkNodeFlagNone
نود قبلیمجاز
نود بعدیمجاز، در استفاده معمول لازم
layer groupkNodeLayerAnything
required_padding_left1

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

  • بعد از TcpOverUdpClient نود stream-only نگذارید؛ سمت بعدی باید packet boundary را حفظ کند.
  • FEC را فقط در یک سمت فعال نکنید.
  • تعداد FEC shardها باید در هر دو سمت یکسان باشد.
  • تصور نکنید KCP جلوی هر نوع packet loss را می‌گیرد؛ KCP داده را دوباره می‌فرستد و FEC اختیاری در برابر loss متوسط کمک می‌کند، اما loss شدید یا bursty همچنان مشکل‌ساز است.
  • no-recv-timeout-ms را کمتر یا مساوی ping-interval-ms نگذارید.