Direct Sockets

Demián Renzulli
Demián Renzulli
Andrew Rayskiy
Andrew Rayskiy
Vlad Krot
Vlad Krot

आम तौर पर, स्टैंडर्ड वेब ऐप्लिकेशन को कुछ खास कम्यूनिकेशन प्रोटोकॉल, जैसे कि एचटीटीपी और एपीआई, जैसे कि WebSocket और WebRTC का इस्तेमाल करने की अनुमति होती है. ये शक्तिशाली हैं, लेकिन इन्हें इस तरह से डिज़ाइन किया गया है कि इनका गलत इस्तेमाल न हो. ये रॉ टीसीपी या यूडीपी कनेक्शन नहीं बना सकते. इससे वेब ऐप्लिकेशन, लेगसी सिस्टम या ऐसे हार्डवेयर डिवाइसों से कम्यूनिकेट नहीं कर पाते जो अपने नॉन-वेब प्रोटोकॉल का इस्तेमाल करते हैं. उदाहरण के लिए, आपको वेब पर आधारित एसएसएच क्लाइंट बनाना हो सकता है, किसी लोकल प्रिंटर से कनेक्ट करना हो सकता है या आईओटी डिवाइसों के फ़्लीट को मैनेज करना हो सकता है. पहले, इसके लिए ब्राउज़र प्लगिन या नेटिव हेल्पर ऐप्लिकेशन की ज़रूरत होती थी.

Direct Sockets API, इस समस्या को हल करता है. यह आइसोलेटेड वेब ऐप्लिकेशन (आईडब्ल्यूए) को रिले सर्वर के बिना, सीधे टीसीपी और यूडीपी कनेक्शन बनाने की अनुमति देता है. आईडब्ल्यूए में, सुरक्षा के अतिरिक्त उपायों की वजह से इस एपीआई को सुरक्षित तरीके से इस्तेमाल किया जा सकता है. जैसे, कॉन्टेंट की सुरक्षा के लिए सख्त नीति (सीएसपी) और क्रॉस-ऑरिजिन आइसोलेशन.

उपयोग के उदाहरण

आपको स्टैंडर्ड WebSockets के बजाय Direct Sockets का इस्तेमाल कब करना चाहिए?

  • IoT और स्मार्ट डिवाइस: ऐसे हार्डवेयर से कम्यूनिकेट करना जो एचटीटीपी के बजाय रॉ टीसीपी/यूडीपी का इस्तेमाल करता है.
  • लेगसी सिस्टम: पुराने मेल सर्वर (एसएमटीपी/आईएमएपी), आईआरसी चैट सर्वर या प्रिंटर से कनेक्ट करना.
  • रिमोट डेस्कटॉप और टर्मिनल: एसएसएच, टेलनेट या आरडीपी क्लाइंट लागू करना.
  • P2P सिस्टम: डिस्ट्रिब्यूटेड हैश टेबल (डीएचटी) या भरोसेमंद सहयोग टूल (जैसे, आईपीएफ़एस) लागू करना.
  • मीडिया ब्रॉडकास्टिंग: एक साथ कई एंडपॉइंट पर कॉन्टेंट स्ट्रीम करने के लिए, यूडीपी का इस्तेमाल करना (मल्टीकास्टिंग). इससे खुदरा दुकानों पर लगे कियॉस्क के नेटवर्क पर, वीडियो को एक साथ चलाने जैसे इस्तेमाल के उदाहरणों को लागू किया जा सकता है.
  • सर्वर और लिसनर की सुविधाएं: IWA को कॉन्फ़िगर किया जा सकता है, ताकि वह TCPServerSocket या बाउंड UDPSocket का इस्तेमाल करके, आने वाले टीसीपी कनेक्शन या यूडीपी डेटाग्राम के लिए, रिसीविंग एंडपॉइंट के तौर पर काम कर सके.

Direct Sockets के लिए ज़रूरी शर्तें

Direct Sockets का इस्तेमाल करने से पहले, आपको काम करने वाला आईडब्ल्यूए सेट अप करना होगा. इसके बाद, अपने पेजों में Direct Sockets को इंटिग्रेट किया जा सकता है.

अनुमति से जुड़ी नीति जोड़ना

Direct Sockets का इस्तेमाल करने के लिए, आपको अपने आईडब्ल्यूए मेनिफ़ेस्ट में permissions_policy ऑब्जेक्ट को कॉन्फ़िगर करना होगा. एपीआई को साफ़ तौर पर चालू करने के लिए, आपको direct-sockets कुंजी जोड़नी होगी. इसके अलावा, आपको cross-origin-isolated बटन को भी शामिल करना होगा. यह कुंजी, Direct Sockets के लिए खास नहीं है. हालांकि, यह सभी आईडब्ल्यूए के लिए ज़रूरी है. इससे यह तय होता है कि दस्तावेज़, उन एपीआई को ऐक्सेस कर सकता है जिनके लिए क्रॉस-ऑरिजिन आइसोलेशन की ज़रूरत होती है.

{
  "permissions_policy": {
    "direct-sockets": ["self"],
    "cross-origin-isolated": ["self"]
  }
}

direct-sockets कुंजी से यह तय होता है कि new TCPSocket(...), new TCPServerSocket(...) या new UDPSocket(...) पर कॉल करने की अनुमति है या नहीं. अगर इस नीति को सेट नहीं किया जाता है, तो ये कंस्ट्रक्टर तुरंत NotAllowedError के साथ अस्वीकार कर देंगे.

TCPSocket लागू करना

ऐप्लिकेशन, TCPSocket इंस्टेंस बनाकर टीसीपी कनेक्शन का अनुरोध कर सकते हैं.

कनेक्शन खोलना

कनेक्शन खोलने के लिए, new ऑपरेटर और await खोले गए प्रॉमिस का इस्तेमाल करें.

TCPSocket कंस्ट्रक्टर, बताए गए remoteAddress और remotePort का इस्तेमाल करके कनेक्शन शुरू करता है.

const remoteAddress = 'example.com';
const remotePort = 7;

// Configure options like keepAlive or buffering
const options = {
  keepAlive: true,
  keepAliveDelay: 720000
};

let tcpSocket = new TCPSocket(remoteAddress, remotePort, options);

// Wait for the connection to be established
let { readable, writable } = await tcpSocket.opened;

वैकल्पिक कॉन्फ़िगरेशन ऑब्जेक्ट की मदद से, नेटवर्क को बेहतर तरीके से कंट्रोल किया जा सकता है. इस मामले में, keepAliveDelay को 720000 मिलीसेकंड पर सेट किया गया है, ताकि गतिविधि न होने पर भी कनेक्शन बना रहे. डेवलपर यहां अन्य प्रॉपर्टी भी कॉन्फ़िगर कर सकते हैं. जैसे, noDelay. इससे नेगल के एल्गोरिदम को बंद किया जा सकता है, ताकि सिस्टम छोटे पैकेट को बैच करने से रुक जाए. इससे संभावित रूप से लेटेन्सी कम हो जाती है. इसके अलावा, थ्रूपुट को मैनेज करने के लिए sendBufferSize और receiveBufferSize का इस्तेमाल किया जा सकता है.

ऊपर दिए गए स्निपेट के आखिरी हिस्से में, कोड खुले हुए प्रॉमिस का इंतज़ार करता है. यह प्रॉमिस सिर्फ़ तब पूरा होता है, जब हैंडशेक पूरा हो जाता है. इसके बाद, यह TCPSocketOpenInfo ऑब्जेक्ट दिखाता है. इस ऑब्जेक्ट में, डेटा ट्रांसफ़र के लिए ज़रूरी रीडेबल और राइटेबल स्ट्रीम होती हैं.

पढ़ने और लिखने का ऐक्सेस

सॉकेट खुलने के बाद, स्टैंडर्ड Streams API इंटरफ़ेस का इस्तेमाल करके उससे इंटरैक्ट करें.

  • लिखने की सुविधा: लिखने की सुविधा वाली स्ट्रीम, BufferSource (जैसे कि ArrayBuffer) स्वीकार करती है.
  • रीडिंग: पढ़ने लायक स्ट्रीम से Uint8Array डेटा मिलता है.
// Writing data
const writer = writable.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode("Hello Server"));

// Call when done
writer.releaseLock();

// Reading data
const reader = readable.getReader();
const { value, done } = await reader.read();
if (!done) {
    const decoder = new TextDecoder();
    console.log("Received:", decoder.decode(value));
}

// Call when done
reader.releaseLock();

BYOB की मदद से पढ़ने का बेहतर अनुभव

ज़्यादा परफ़ॉर्मेंस वाले ऐप्लिकेशन के लिए, मेमोरी के बंटवारे को मैनेज करना ज़रूरी होता है. इसलिए, API "Bring Your Own Buffer" (BYOB) रीडिंग का समर्थन करता है. ब्राउज़र को मिले डेटा के हर हिस्से के लिए नया बफ़र असाइन करने के बजाय, रीडर को पहले से असाइन किया गया बफ़र पास किया जा सकता है. यह सीधे आपकी मौजूदा मेमोरी में डेटा लिखता है. इससे गार्बेज कलेक्शन का ओवरहेड कम हो जाता है.

// 1. Get a BYOB reader explicitly
const reader = readable.getReader({ mode: 'byob' });

// 2. Allocate a reusable buffer (e.g., 4KB)
let buffer = new Uint8Array(4096);

// 3. Read directly into the existing buffer
const { value, done } = await reader.read(buffer);

if (!done) {
  // 'value' is a view of the data written directly into your buffer
  console.log("Bytes received:", value.byteLength);
}

reader.releaseLock();

UDPSocket को लागू करना

UDPSocket क्लास, यूडीपी कम्यूनिकेशन की अनुमति देती है. यह दो अलग-अलग मोड में काम करता है. यह इस बात पर निर्भर करता है कि आपने विकल्पों को कैसे कॉन्फ़िगर किया है.

कनेक्टेड मोड

इस मोड में, सॉकेट किसी एक डेस्टिनेशन से कम्यूनिकेट करता है. यह स्टैंडर्ड क्लाइंट-सर्वर टास्क के लिए काम का है.

// Connect to a specific remote host
let udpSocket = new UDPSocket({
    remoteAddress: 'example.com',
    remotePort: 7 });

let { readable, writable } = await udpSocket.opened;

बाउंड मोड

इस मोड में, सॉकेट को लोकल आईपी एंडपॉइंट से बाइंड किया जाता है. यह किसी भी सोर्स से डेटाग्राम पा सकता है और उन्हें किसी भी डेस्टिनेशन पर भेज सकता है. इसका इस्तेमाल अक्सर, लोकल डिस्कवरी प्रोटोकॉल या सर्वर की तरह काम करने वाले व्यवहार के लिए किया जाता है.

// Bind to all interfaces (IPv6)
let udpSocket = new UDPSocket({
    localAddress: '::'
    // omitting localPort lets the OS pick one
});

// localPort will tell you the OS-selected port.
let { readable, writable, localPort } = await udpSocket.opened;

यूडीपी मैसेज मैनेज करना

बाइट की टीसीपी स्ट्रीम के उलट, यूडीपी स्ट्रीम UDPMessage ऑब्जेक्ट में काम करती हैं. इनमें डेटा और रिमोट पते की जानकारी होती है. यहां दिए गए कोड से पता चलता है कि "बाउंड मोड" में UDPSocket का इस्तेमाल करते समय, इनपुट/आउटपुट कार्रवाइयों को कैसे मैनेज किया जाता है.

// Writing (Bound Mode requires specifying destination)
const writer = writable.getWriter();
await writer.write({
    data: new TextEncoder().encode("Ping"),
    remoteAddress: '192.168.1.50',
    remotePort: 8080
});

// Reading
const reader = readable.getReader();
const { value } = await reader.read();
// value contains: { data, remoteAddress, remotePort }
console.log(`Received from ${value.remoteAddress}:`, value.data);

"कनेक्टेड मोड" में, सॉकेट किसी खास पियर से लॉक होता है. हालांकि, बाउंड मोड में सॉकेट किसी भी डेस्टिनेशन से कम्यूनिकेट कर सकता है. इसलिए, जब लिखने लायक स्ट्रीम में डेटा लिखा जाता है, तब आपको एक UDPMessage ऑब्जेक्ट पास करना होगा. इसमें हर पैकेट के लिए remoteAddress और remotePort की जानकारी साफ़ तौर पर दी गई हो. इससे सॉकेट को यह पता चलेगा कि उस खास डेटाग्राम को कहां भेजना है. इसी तरह, रीडेबल स्ट्रीम से डेटा पढ़ते समय, रिटर्न की गई वैल्यू में सिर्फ़ डेटा पेलोड ही नहीं, बल्कि भेजने वाले का remoteAddress और remotePort भी शामिल होता है. इससे आपका ऐप्लिकेशन, हर इनकमिंग पैकेट के सोर्स की पहचान कर पाता है.

ध्यान दें: "कनेक्टेड मोड" में UDPSocket का इस्तेमाल करने पर, सॉकेट किसी खास पीयर से लॉक हो जाता है. इससे I/O प्रोसेस आसान हो जाती है. इस मोड में, डेस्टिनेशन पहले से तय होता है. इसलिए, लिखते समय remoteAddress और remotePort प्रॉपर्टी का कोई असर नहीं पड़ता. इसी तरह, मैसेज पढ़ते समय ये प्रॉपर्टी null वैल्यू दिखाती हैं, क्योंकि सोर्स कनेक्ट किया गया पीयर होता है.

मल्टीकास्ट की सुविधा

कई कियॉस्क पर वीडियो चलाने की सुविधा को सिंक करने या लोकल डिवाइस डिस्कवरी (उदाहरण के लिए, mDNS) लागू करने जैसे इस्तेमाल के उदाहरणों के लिए, Direct Sockets, मल्टीकास्ट यूडीपी के साथ काम करता है. इससे मैसेज को "ग्रुप" पते पर भेजा जा सकता है. साथ ही, नेटवर्क पर मौजूद सभी सदस्य इसे पा सकते हैं. ऐसा किसी एक सदस्य के बजाय किया जाता है.

मल्टीकास्ट करने की अनुमतियां

मल्टीकास्ट की सुविधाओं का इस्तेमाल करने के लिए, आपको अपने आईडब्ल्यूए मेनिफ़ेस्ट में direct-sockets-multicast अनुमति जोड़नी होगी. यह स्टैंडर्ड डायरेक्ट-सॉकेट की अनुमति से अलग है. साथ ही, यह ज़रूरी है, क्योंकि मल्टीकास्ट का इस्तेमाल सिर्फ़ निजी नेटवर्क में किया जाता है.

{
  "permissions_policy": {
    "direct-sockets": ["self"],
    "direct-sockets-multicast": ["self"],
    "direct-sockets-private": ["self"],
    "cross-origin-isolated": ["self"]
  }
}

मल्टीकास्ट डेटाग्राम भेजना

मल्टीकास्ट ग्रुप को डेटा भेजना, स्टैंडर्ड यूडीपी "कनेक्टेड मोड" की तरह ही होता है. हालांकि, इसमें पैकेट के व्यवहार को कंट्रोल करने के लिए कुछ खास विकल्प जोड़े जाते हैं.

const MULTICAST_GROUP = '239.0.0.1';
const PORT = 12345;

const socket = new UDPSocket({
  remoteAddress: MULTICAST_GROUP,
  remotePort: PORT,
  // Time To Live: How many router hops the packet can survive (default: 1)
  multicastTimeToLive: 5,
  // Loopback: Whether to receive your own packets (default: true)
  multicastLoopback: true
});

const { writable } = await socket.opened;
// Write to the stream as usual...

मल्टीकास्ट डेटाग्राम पाना

मल्टीकास्ट ट्रैफ़िक पाने के लिए, आपको "बाउंड मोड" में UDPSocket खोलना होगा. आम तौर पर, यह 0.0.0.0 या :: से बाइंड होता है. इसके बाद, MulticastController का इस्तेमाल करके किसी ग्रुप में शामिल होना होगा. multicastAllowAddressSharing विकल्प (Unix पर SO_REUSEADDR जैसा) का इस्तेमाल भी किया जा सकता है. यह डिवाइस डिस्कवरी प्रोटोकॉल के लिए ज़रूरी है. इसमें एक ही डिवाइस पर मौजूद कई ऐप्लिकेशन को एक ही पोर्ट पर सुनना होता है.

const socket = new UDPSocket({
  localAddress: '0.0.0.0', // Listen on all interfaces
  localPort: 12345,
  multicastAllowAddressSharing: true // Allow multiple applications to bind to the same address / port pair.
});

// The open info contains the MulticastController
const { readable, multicastController } = await socket.opened;

// Join the group to start receiving packets
await multicastController.joinGroup('239.0.0.1');

const reader = readable.getReader();

// Read the stream...
const { value } = await reader.read();
console.log(`Received multicast from ${value.remoteAddress}`);

// When finished, you can leave the group (this is an optional, but recommended practice)
await multicastController.leaveGroup('239.0.0.1');

सर्वर बनाना

यह एपीआई, टीसीपी कनेक्शन स्वीकार करने के लिए TCPServerSocket का भी इस्तेमाल करता है. इससे आपका IWA, लोकल सर्वर के तौर पर काम कर पाता है. यहां दिए गए कोड में, TCPServerSocket इंटरफ़ेस का इस्तेमाल करके टीसीपी सर्वर बनाने का तरीका बताया गया है.

// Listen on all interfaces (IPv6)
let tcpServerSocket = new TCPServerSocket('::');

// Accept connections via the readable stream
let { readable } = await tcpServerSocket.opened;
let reader = readable.getReader();

// Wait for a client to connect
let { value: clientSocket } = await reader.read();

// 'clientSocket' is a standard TCPSocket you can now read/write to

'::' पते के साथ क्लास को इंस्टैंटिएट करने पर, सर्वर सभी उपलब्ध IPv6 नेटवर्क इंटरफ़ेस से जुड़ जाता है, ताकि आने वाले कनेक्शन के अनुरोधों को सुना जा सके. कॉलबैक पर आधारित पारंपरिक सर्वर एपीआई के उलट, यह एपीआई वेब के Streams API पैटर्न का इस्तेमाल करता है: आने वाले कनेक्शन, ReadableStream के तौर पर डिलीवर किए जाते हैं. जब कॉल reader.read() किया जाता है, तो ऐप्लिकेशन, कतार में मौजूद अगले कनेक्शन का इंतज़ार करता है और उसे स्वीकार करता है. इससे एक ऐसी वैल्यू मिलती है जो पूरी तरह से काम करने वाला TCPSocket इंस्टेंस होता है. यह इंस्टेंस, उस क्लाइंट के साथ दोनों तरफ़ से कम्यूनिकेट करने के लिए तैयार होता है.

Chrome DevTools की मदद से Direct Sockets को डीबग करना

Chrome 138 से, Chrome DevTools में नेटवर्क पैनल में जाकर, डायरेक्ट सॉकेट के ट्रैफ़िक को सीधे तौर पर डीबग किया जा सकता है. इससे बाहरी पैकेट स्निफ़र की ज़रूरत नहीं पड़ती. इस टूल की मदद से, स्टैंडर्ड एचटीटीपी अनुरोधों के साथ-साथ, TCPSocket कनेक्शन और UDPSocket ट्रैफ़िक (बाउंड और कनेक्टेड, दोनों मोड में) को मॉनिटर किया जा सकता है.

अपने ऐप्लिकेशन की नेटवर्क गतिविधि की जांच करने के लिए:

  1. Chrome DevTools में नेटवर्क पैनल खोलें.
  2. अनुरोध टेबल में, सॉकेट कनेक्शन ढूंढें और उसे चुनें.
  3. भेजे और पाए गए सभी डेटा का लॉग देखने के लिए, Messages टैब खोलें.

DevTools में मौजूद Messages टैब में मौजूद डेटा.

इस व्यू में हेक्स व्यूअर होता है. इसकी मदद से, टीसीपी और यूडीपी मैसेज के रॉ बाइनरी पेलोड की जांच की जा सकती है. इससे यह पक्का किया जा सकता है कि प्रोटोकॉल को सही तरीके से लागू किया गया है.

डेमो

IWA Kitchen Sink में एक ऐसा ऐप्लिकेशन है जिसमें कई टैब हैं. हर टैब, अलग-अलग IWA API के बारे में बताता है. जैसे, Direct Sockets, Controlled Frame वगैरह.

इसके अलावा, telnet client demo में एक Isolated Web App शामिल है. इससे उपयोगकर्ता, इंटरैक्टिव टर्मिनल के ज़रिए टीसीपी/आईपी सर्वर से कनेक्ट कर सकता है. दूसरे शब्दों में, टेलनेट क्लाइंट.

नतीजा

Direct Sockets API, एक ज़रूरी फ़ंक्शन के बीच के अंतर को कम करता है. यह वेब ऐप्लिकेशन को ऐसे रॉ नेटवर्क प्रोटोकॉल को हैंडल करने की सुविधा देता है जिन्हें पहले नेटिव रैपर के बिना इस्तेमाल नहीं किया जा सकता था. यह सिर्फ़ क्लाइंट कनेक्टिविटी से ज़्यादा है. TCPServerSocket की मदद से, ऐप्लिकेशन आने वाले कनेक्शन के लिए सुन सकते हैं. वहीं, UDPSocket पीयर-टू-पीयर कम्यूनिकेशन और लोकल नेटवर्क डिस्कवरी, दोनों के लिए फ़्लेक्सिबल मोड उपलब्ध कराता है.

मॉडर्न Streams API के ज़रिए, टीसीपी और यूडीपी की इन क्षमताओं को उपलब्ध कराकर, अब सीधे JavaScript में लेगसी प्रोटोकॉल—जैसे कि SSH, RDP या कस्टम IoT स्टैंडर्ड—के सभी फ़ीचर वाले वर्शन बनाए जा सकते हैं. इस एपीआई से नेटवर्क का ऐक्सेस कम लेवल पर मिलता है. इसलिए, इससे सुरक्षा पर काफ़ी असर पड़ता है. इसलिए, इसे आइसोलेटेड वेब ऐप्लिकेशन (आईडब्ल्यूए) के लिए प्रतिबंधित किया गया है. इससे यह पक्का किया जाता है कि इस तरह की सुविधा सिर्फ़ उन भरोसेमंद ऐप्लिकेशन को दी जाए जिन्हें साफ़ तौर पर इंस्टॉल किया गया है और जो सुरक्षा से जुड़ी सख्त नीतियों को लागू करते हैं. इस बैलेंस की मदद से, डिवाइस के हिसाब से काम करने वाले असरदार ऐप्लिकेशन बनाए जा सकते हैं. साथ ही, यह भी पक्का किया जा सकता है कि वेब प्लैटफ़ॉर्म से लोगों को मिलने वाली सुरक्षा बनी रहे.

संसाधन