# المقابس

استخدم مقبس SDK لتوصيل عميل TCP أو UDP في تطبيقك عبر جهاز Device من URnetwork. وله سلوك مألوف في القراءة والكتابة والمواعيد النهائية والإغلاق، بينما تسلك حركتُه مسارَ الاتصال الذي اختاره الجهاز. وتستطيع استخدامه لعميل واحد من دون أن تمرّر بقية النظام عبر شبكة VPN لنظام التشغيل.

ابدأ بجهاز مُهيّأ كما في [البدء مع SDK](/docs/getting-started-sdk). ودوال المقابس متاحة في مصدر SDK المحدَّث، وتتطلب دعماً مطابقاً للمقابس على الجهاز البعيد. فاستخدم على جانبَي اتصال RPC بناءاتٍ من SDK تتضمن واجهات API هذه.

واستخدم [التثبيت](/docs/install-sdk) لأوامر مديري الحزم الأصلية، و[الأمثلة](/docs/examples) لتكاملات قابلة للتشغيل مع المقابس ومكتبات HTTP في اثنتي عشرة لغة، ومنها برامج منفصلة لـJavaScript على Node وفي المتصفح.

## بماذا يختلف عن مقبس نواة النظام

مقبس نواة النظام ملكٌ لمكدّس الشبكات في نظام التشغيل. أما مقبس SDK فملكٌ للجهاز، ويستخدم مكدّس TCP/IP داخل العملية. ويرسل الجهاز حزمه عبر المسار المُعدّ له، ومن ذلك المزوّد الذي اختاره حين يكون متصلاً عبر URnetwork.

| | مقبس نواة النظام | مقبس SDK |
| --- | --- | --- |
| مسار الشبكة | جدول التوجيه في نظام التشغيل واختيار الواجهة | توجيه الجهاز، واختيار المزوّد، والسياسة |
| واجهة التطبيق | واصف ملف أو كائن مقبس خاص بالمنصة | `net.Conn` في Go، أو `Conn` في JS، أو مقبض C، أو `Socket` على الهاتف |
| النطاق | اتصال تطبيق على شبكة المضيف | اتصال تطبيق يملكه جهاز واحد |
| الحاجة إلى VPN للنظام من أجل هذا الاتصال | يتوقف على طريقة إعداد مسار نظام التشغيل | لا يلزم وجود واجهة TUN/VPN في نظام التشغيل لاستدعاءات مقبس SDK وحدها |
| العنوان المحلي | واجهة المضيف/فضاء عناوينه | عنوان افتراضي في مكدّس الجهاز داخل فضاء المستخدم |
| إغلاق الجهاز | لا علاقة له | يغلق اتصالات SDK التابعة له |
| خيارات المقبس والمستمعون | واجهات API لنظام التشغيل تختلف بحسب المنصة | اتصالات صادرة فقط؛ بلا واصف ملف، ولا خيارات مقبس اعتباطية، ولا واجهة API للاستماع |

و`localAddr` الافتراضي ليس عنوان IP العام الذي يخرج منه المزوّد، وربطُ مقبسٍ به (bind) لا يجعل تطبيقك قابلاً للوصول من الإنترنت. ويظل اتصال URnetwork الأساسي والمزوّد قادرَين على استخدام شبكات نظام التشغيل. ولا يغيّر مقبس SDK من تلقاء نفسه وضعَ التوجيه المُعدّ للجهاز، ولا يُنشئ اتصالاً بمزوّد.

يشفّر TLS وDTLS اتصالَ التطبيق حتى وجهته. أما TCP وUDP العاديان فيحفظان بروتوكول التطبيق نصاً صريحاً كما هو؛ وتشفير النقل الذي تستخدمه URnetwork حتى المزوّد لا يحوّل ذلك البروتوكول إلى TLS حتى الوجهة.

## الشبكات وأسماء المضيفين

اتصل بـ`host:port` باستخدام إحدى قيم الشبكة التالية:

| الشبكة | السلوك |
| --- | --- |
| `tcp` | TCP مع Happy Eyeballs عبر IPv4/IPv6 لاسم المضيف |
| `tcp4` و`tcp6` | TCP مقصور على عائلة العناوين تلك |
| `udp` | UDP؛ واسم المضيف ذو العائلتين يختار نظيره عبر سباقٍ بأول مخطط بيانات (datagram) وردّه |
| `udp4` و`udp6` | UDP مقصور على عائلة العناوين تلك |

ضع عناوين IPv6 الحرفية بين أقواس معقوفة، مثل `[2001:db8::10]:443`. ويستخدم تحليلُ أسماء المضيفين محلّلَ المقابس الخاص بالجهاز ومسارَ حزمه. ولا يرتد إلى تحليل الوجهة أو فتحها مباشرةً عبر المضيف حين لا يتوفر مزوّد.

ويُسابق Happy Eyeballs في TCP بين محاولات الاتصال. أما دوال الاتصال لـTLS/DTLS فتختار عائلةً بعد نجاح مصافحتها الآمنة. وأسماء العائلات الصريحة والعناوين الحرفية تتجاوز السباق بين العائلتين.

### UDP: احتمال التسليم المكرر

ليس لـUDP مصافحة اتصال. ومع `udp` حين يوجد سجلّا A وAAAA معاً، تذهب الكتابة الأولى إلى IPv6، ثم إلى IPv4 بعد 250 ms من دون رد. وإخفاقٌ فوري في الكتابة عبر IPv6 يبدأ الارتداد أسرع. **وقد يصل مخطط البيانات الأول من التطبيق إلى العنوانين كليهما.** وأول رد يختار النظير، ولا تذهب الكتابات اللاحقة إلا إلى ذلك النظير.

استخدم معرّفات للطلبات أو آلية أخرى على مستوى التطبيق لمعالجة التكرار حين يهمّ التسليم المكرر. فنجاح الكتابة الأولى لا يثبت التسليم ولا يختار نظيراً. وإن لم يردّ الخادم أبداً، تنتظر الكتابات اللاحقة الاختيارَ حتى موعدها النهائي أو الإغلاق. وللبروتوكولات التي ترسل فقط، استخدم `udp4` أو `udp6` أو عنوان IP حرفياً. والرد الذي طوله صفر بايت ردٌّ صالح.

كل كتابة UDP مخطط بيانات واحد. وكل قراءة تستهلك مخطط بيانات واحداً؛ ومخزن القراءة الصغير يُسقط البايتات المتبقية. ومخطط البيانات الفارغ بياناتٌ، لا EOF. وأبقِ الرسائل ضمن حدود بروتوكول الوجهة والمسار.

## Go

يوفّر `DeviceLocal` و`DeviceRemote` الدوالَّ `Dial` و`DialContext` و`DialTls` و`DialTlsContext`. وتستخدم دوال الاتصال العادية التواقيعَ نفسها التي يستخدمها `net.Dialer` في Go، وتعيد اتصالاً متوافقاً مع `net.Conn`.

```go
// device is an initialized sdk.Device.
conn, err := device.DialContext(ctx, "tcp", "example.com:80")
if err != nil {
    return err
}
defer conn.Close()
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
    return err
}
_, err = conn.Write([]byte("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"))
```

ويستطيع عميل HTTP موجود أن يستخدم الجهاز مباشرةً:

```go
transport := &http.Transport{DialContext: device.DialContext}
defer transport.CloseIdleConnections()
client := &http.Client{Transport: transport, Timeout: 20 * time.Second}
// net/http performs normal HTTPS certificate verification above this dialer.
```

وللتشفير المباشر، نادِ `device.DialTlsContext(ctx, "tcp", "example.com:443", nil)`. فيستخدم TCP بروتوكول TLS، ويستخدم UDP بروتوكول DTLS 1.2. وإعداد TLS بقيمة nil يستخدم التحقق العادي من الشهادات. واسم المضيف الأصلي يوفّر اسم الخادم الافتراضي. ويدعم DTLS سطحَ إعداد أصغر من TLS في Go، ويرفض خيارات الأمان غير المدعومة.

وقد تعيد القراءة والكتابة بيانات جزئية مع خطأ؛ فعالج البايتات المُعادة. وسياق الاتصال (dial context) يتحكم في إنشاء الاتصال، لا في عمر الاتصال الناجح. فاضبط مواعيد نهائية مطلقة لعمليات الإدخال/الإخراج اللاحقة، واستخدم `time.Time{}` لمسحها. وأغلق الاتصال حين تنتهي. والحدّ الافتراضي لمدة إنشاء الاتصال والمصافحة الآمنة 30 ثانية، ويقصر إن كان لدى المستدعي موعد نهائي أبكر.

## JavaScript وTypeScript

توفّر أغلفة Device في SDK الدالتين `dial` و`dialTls` القائمتين على Promise. وتستخدم صفحة المتصفح جهازاً بعيداً مُعَدّاً يدعم المقابس. وتكشف SDK الاتصال البعيد عبر WASM؛ فلا تتطلب دعماً أصلياً لـDirect Sockets في المتصفح، ولا تغليفاً بوصفها تطبيق ويب معزولاً (Isolated Web App).

```js
const conn = await device.dialTls("tcp", "example.com:443", undefined, {
  timeoutMillis: 5000,
});
try {
  await conn.setDeadline(Date.now() + 5000);
  await conn.write(new TextEncoder().encode(
    "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"
  ));
  for (let chunk; (chunk = await conn.read()) !== null;) {
    console.log(new TextDecoder().decode(chunk));
  }
} finally {
  await conn.close();
}
```

تعيد `read()` كائن `Uint8Array`، أو `null` عند EOF. والمصفوفة الفارغة مخطط بيانات UDP صالح. ويوفّر `readable` و`writable` محوّلات Web Streams؛ فاختر لكل اتجاه إما الدوال المباشرة وإما المحوّلات. وإغلاق تدفق TCP يرسل FIN من جانب الكتابة حيث يكون ذلك مدعوماً. أما `close()` الصريحة فتغلق الاتصال كله.

استخدم `AbortSignal` لإلغاء الفتح. أما المقابس التي أُنشئت فتستخدم المواعيد النهائية والإغلاق. وتقبل دوال المواعيد النهائية عدد المللي ثانية منذ الحقبة (epoch)، أو `Date`، أو `null` للمسح. ويتضمن خطأ الكتابة الجزئية `bytesWritten`؛ وتُعاد بيانات القراءة الجزئية قبل الخطأ المصاحب لها في القراءة المباشرة التالية. وتتوقف حركة المقبس حين تنقطع الخدمة البعيدة التي تملكه؛ ولا يُعاد إرسالها بعد إعادة الاتصال.

### واجهة Direct Sockets

استخدم تواقيع المُنشئات في Direct Sockets مع جهاز Device من UR:

```js
const {TCPSocket, UDPSocket} = device.directSockets;
const socket = new TCPSocket("echo.example", 9000);
const {readable, writable} = await socket.opened;
const reader = readable.getReader({mode: "byob"});
const writer = writable.getWriter();
try {
  await writer.write(new TextEncoder().encode("hello"));
  await writer.close();
  console.log(await reader.read(new Uint8Array(1024)));
} finally {
  await Promise.allSettled([reader.cancel(), writer.abort()]);
  reader.releaseLock(); writer.releaseLock();
  await socket.close();
  await socket.closed;
}
```

ولـUDP، أنشئ `new UDPSocket({remoteAddress: "echo.example", remotePort: 9001})`. وانتظر `opened`، ثم اكتب `{data: new Uint8Array([1, 2, 3])}` واقرأ `value.data`. وكل كائن مخطط بيانات واحد، ومن ذلك مخطط البيانات الذي طوله صفر بايت. ورسائل UDP المتصل تخلو من حقول العنوان البعيد، وترفض تجاوز الوجهة لكل رسالة.

ويعيد المصنع المُصدَّر `createDirectSockets(device)` هذه المُنشئات أيضاً. ولكلتا الواجهتين وعدا `opened` و`closed`، ودالة `close()` غير متزامنة. ويدعم TCP القارئات الافتراضية وقارئات BYOB، وكتابة قيم BufferSource. وإغلاق تدفقه القابل للكتابة يرسل FIN بينما تبقى القراءة متاحة. ألغِ العمليات المعلّقة أو أجهضها، وحرّر أقفال القارئ/الكاتب قبل إغلاق المقبس؛ وإلا فإن `close()` ترفض بالخطأ `InvalidStateError`. وإخفاقات الشبكة ترفض بالخطأ `NetworkError`.

والواجهة الأصلية في Chrome متاحة لتطبيقات الويب المعزولة (Isolated Web Apps). أما تنفيذ SDK فيعمل في المتصفحات العادية وفي Node عبر الجهاز المُعدّ، من دون حزمة IWA ولا إذنٍ أصلي لـDirect Sockets. وهو لا يستبدل المتغيرات العامة للمتصفح. انظر [وثائق Chrome](https://developer.chrome.com/docs/iwa/direct-sockets) و[مقترح Direct Sockets](https://wicg.github.io/direct-sockets/).

ويُنفّذ هذا الإصدار TCP الصادر وUDP المتصل. استخدم `dnsQueryType: "ipv4"` أو `"ipv6"` لتقييد التحليل؛ وإغفاله يُبقي على Happy Eyeballs الخاص بالجهاز. أما UDP المربوط، والبث المتعدد، والمستمعون، وضبط المخزن المؤقت وno-delay وkeep-alive لكل مقبس، فغير متاحة، والطلبات الصالحة لتلك الميزات تُخفق بالخطأ `NotSupportedError`. فهذا ملف توافق للعملاء، لا واجهة المتصفح الكاملة.

ومع أسماء UDP ذات العائلتين، يتحقق وعد `opened` قبل الإرسال. وتصف حقول نقطة النهاية فيه المرشّحَ الأول في البداية، ثم تتحدّث بعد استهلاك أول رد. **وقد يصل مخطط البيانات الأول إلى العنوانين كليهما.** فاختر عنوان IP حرفياً أو عائلة DNS حين تحتاج إلى نقطة نهاية ثابتة عند الفتح. وتستخدم الأمثلة مؤقِّتاً لإلغاء عمليات الإدخال/الإخراج المتعثرة على التدفقات؛ كما توفّر واجهة `Conn` المنفصلة مواعيد نهائية.

وتفتح مُنشئات Direct Sockets اتصالات TCP/UDP عادية. استخدم `dialTls` لـTLS/DTLS. وتتضمن [أمثلة JavaScript لـNode والمتصفح](/docs/examples/javascript) و[مثال TypeScript](/docs/examples/typescript) القابلة للتشغيل صدى TCP/UDP، والمهل الزمنية، والتنظيف، والتكامل مع مكتبات HTTP.

## طبقات الربط الأصلية والمنصات المدعومة

| طبقة الربط | سطح المقابس والمنصة |
| --- | --- |
| Go | سلوك `net.Conn` على أهداف Go التي تدعمها SDK |
| Android Kotlin/Java | `OpenSocket`/`Socket` القابلة للنقل في AAR؛ ومستوى API 24+ على Android مع واجهات ABI المشحونة |
| Apple Swift | `OpenSocket`/`Socket` القابلة للنقل في XCFramework؛ وiOS 16+ وmacOS 13.5+ مع شرائح الجهاز/المحاكي المشحونة |
| C/C++ وعملاء FFI الآخرون | واجهة C ABI لـWindows 10+ وLinux glibc 2.35+ على amd64/arm64؛ وبناءات مضيف macOS للتطوير |
| JS/TS في المتصفح وNode | WASM، وConn، وتدفقات Web Streams في Direct Sockets، إضافة إلى نقطة نهاية Device RPC متاحة تدعم المقابس |

ويستخدم المستدعون على منصات الهاتف `OpenSocket(network, address, timeoutMillis, tlsOptions)`. فخيارات TLS بقيمة nil تطلب اتصالاً عادياً؛ والإعداد بقيمة غير nil يطلب TLS/DTLS. وتعيد `Socket.Read` نتيجة فيها `Data` و`Eof`؛ وتأخذ دوال المواعيد النهائية عدد المللي ثانية منذ الحقبة، والصفر للمسح. واستخدم خيطاً عاملاً أو موزّع coroutine مناسباً للعمليات الحاجبة.

ويستخدم مستدعو C الدالتين `urnet_device_dial` / `urnet_device_dial_tls`، ثم `urnet_conn_read` و`urnet_conn_write` ودوال الموعد النهائي والإغلاق. وتعيد القراءة عدد بايتات وعَلَم EOF منفصلاً. وهي تستهلك البيانات فوراً؛ وليست استعلاماً عن حجم مخزن الإخراج. وعالج أعداد البايتات الجزئية حتى حين تُعاد سلسلة خطأ. وحرّر سلاسل الأخطاء والعناوين بـ`urnet_free_string`، وحرّر مقبض الاتصال بـ`urnet_release`، الذي يغلقه أيضاً.

وتوفّر SDK حالياً اتصالات العملاء. أما مقابس الخادم/الاستماع فستكون إضافة منفصلة، بدلالات صريحة للوصولية والملكية. انظر [جولة SDK](/docs/tour-sdk) لنموذج طبقات الربط والبناء، و[البدء](/docs/getting-started-sdk) لإعداد الجهاز.
