איך להשתמש ב-WebSockets ב-Node.js כדי ליצור אפליקציות בזמן אמת

הדרכה זו מדגימה איך להשתמש ב-WebSockets ב-Node.js לשיחה דו-כיוונית ואינטראקטיבית בין דפדפן לשרת. השיטה חיונית ליישומים מהירים ובזמן אמת כמו דאשבורדים, יישומי צ'אט ומשחקי מולטי-יונר.

Table of Contents

האינטרנט מבוסס על הודעות HTTP של בקשה-תגובה. הדפדפן שלך עושה בקשה לכתובת URL ושרת עונה בנתונים. זה עשוי להוביל לבקשות נוספות של הדפדפן ותגובות של השרת לתמונות, CSS, JavaScript וכד'. אך השרת לא יכול לשלוח נתונים באופן שרירותי לדפדפן.

שיטות עיקול ארוך של Ajax יכולות לגרום ליישומי אינטרנט להיראות כאילו מתעדכנים בזמן אמת, אך התהליך מוגבל מדי עבור יישומים אמיתיים בזמן אמת. בדיקה בכל שניה תהיה לא יעילה בזמנים מסוימים ואיטית מדי באחרים.

לאחר קישור ראשוני מהדפדפן, אירועים שנשלחו על ידי השרת הם תגובה HTTP סטנדרטית (מוקלטת) שיכולה לשלוח הודעות מהשרת בכל זמן. אולם הערוץ הוא חד-כיווני והדפדפן לא יכול לשלוח הודעות חזרה. עבור תקשורת דו-כיוונית מהירה אמיתית, אתה זקוק לWebSocket.

סקירת WebSockets

המונח WebSocket מתייחס לפרוטוקול תקשורת TCP מעל ws:// או המוגנה והמוצפנת wss://. זה שונה מ-HTTP, אם כי הוא יכול לרוץ מעל רצועת תוואי 80 או 443 כדי להבטיח שהוא יעבוד במקומות שחוסמים תקשורת לא-אינטרנטית. רוב הדפדפנים המשוחררים מאז 2012 תומכים בפרוטוקול WebSocket.

ביישום אינטרנטי בזמן אמת טיפוסי, אתה חייב להתקיים לפחות שרת אינטרנט אחד לשרת תוכן אינטרנטי (HTML, CSS, JavaScript, תמונות וכן הלאה) ושרת WebSocket אחד לטפל בתקשורת דו-כיוונית.

הדפדפן עדיין עושה בקשה ראשונית של WebSocket לשרת, שמפתח ערוץ תקשורת. לאחר מכן יכול הדפדפן או השרת לשלוח הודעה בערוץ זה, מה שמעורר אירוע במכשיר השני.

תקשורת עם דפדפנים אחרים המחוברים

לאחר הבקשה הראשונית, הדפדפן יכול לשלוח ולקבל הודעות ל/משרת WebSocket. שרת WebSocket יכול לשלוח ולקבל הודעות ל/מכל אחד מהדפדפנים הלקוחות המחוברים שלו.

תקשורת עמית לעמית אינה אפשרית. BrowserA לא יכול לשלוח הודעה ישירות לBrowserB גם אם הם פועלים על אותו מכשיר ומחוברים לאותו שרת WebSocket! BrowserA יכול רק לשלוח הודעה לשרת ולקוות שהוא יעביר אותה לדפדפנים אחרים כפי שנדרש.

תמיכה בשרת WebSocket

Node.js עדיין אינו מספק תמיכה מקורית בWebSocket, למרות שיש שמועות שזה עומד להגיע בקרוב! למאמר זה, אני משתמש במודול ws של שניים, אך יש עשרות אחרים.

תמיכה מובנית בWebSocket זמינה במערכות הריצה JavaScript Deno ו-Bun.

ספרייות WebSocket זמינות למרבצים כוללים PHP, Python ו-Ruby. אפשרויות של שירותי SaaS של צד שלישי כגון Pusher ו-PubNub מספקים גם שירותי WebSocket מאובטחים.

הדגמת WebSockets מהירה

יישומי צ'אט הם ה-שלום, עולם! של הדגמות WebSocket, אז אני מתנצל על:

  1. חוסר המוצא. עם זאת, יישומי צ'אט הם מעולה להסביר את המושגים.

  2. חוסר היכולת לספק פתרון מקוון מאובטח לחלוטין. כפי שהייתי מעדיף לא לצפות ולפקח על זרם של הודעות אנונימיות!

שכפל או הורד את מאגר ה-node-wschat מ-GitHub:

git clone https://github.com/craigbuckler/node-wschat

התקן את התלמידים של Node.js:

cd node-wschat
npm install

התחל את יישום הצ'אט:

npm start

פתח http://localhost:3000/ במספר דפדפנים או כרטיסיות (אפשר גם להגדיר את שמך לשיחה במחרוזת השאילת מידע — כמו http://localhost:3000/?Craig). הקלד משהו בחלון אחד ולחץ על שלח או הקש על Enter תראה שזה מופיע בכל הדפדפנים המחוברים.

סקירת קוד Node.js

קובץ הכניסה index.js של יישום Node.js מתחיל שני שרתים:

  1. אפליקציית Express פועלת ב http://localhost:3000/ עם תבנית EJS לשרת דף יחיד עם HTML, CSS ו-JavaScript בצד הלקוח. JavaScript הדפדפני משתמש ב-WebSocket API לביצוע החיבור הראשוני ולשליחת וקבלת הודעות.

  2. A WebSocket server running at ws://localhost:3001/, which listens for incoming client connections, handles messages, and monitors disconnections. The full code:

    // שרת WebSocket
    import WebSocket, { WebSocketServer } from 'ws';
    
    const ws = new WebSocketServer({ port: cfg.wsPort });
    
    // חיבור לקוח
    ws.on('connection', (socket, req) => {
    
      console.log(`connection from ${ req.socket.remoteAddress }`);
    
      // הודעה שהתקבלה
      socket.on('message', (msg, binary) => {
    
        // פרסום לכל הלקוחות
        ws.clients.forEach(client => {
          client.readyState === WebSocket.OPEN && client.send(msg, { binary });
        });
    
      });
    
      // סגור
      socket.on('close', () => {
        console.log(`disconnection from ${ req.socket.remoteAddress }`);
      });
    
    });

הספריית Node.js ws:

  • מעלה אירוע "חיבור" כשדפדפן רוצה להתחבר. פונקציית המעבד מקבלת אובייקט סocket המשמש לתקשורת עם כל מכשיר בודד. עליו לשמר אותו לאורך כל תקופת החיבור.

  • מעלה אירוע סocket "הודעה" כשדפדפן שולח הודעה. פונקציית המעבד מפרסמת את ההודעה בחזרה לכל דפדפן מחובר (כולל הדפדפן ששלח אותה).

  • מעלה אירוע סocket "סגירה" כשדפדפן נכבה – בדרך כלל כשהכרטיסיה נסגרת או מתחדשת.

סקירת קוד JavaScript בצד הלקוח

קובץ static/main.js של היישום מריץ פונקציה wsInit() ומעביר את כתובת שרת ה-WebSocket (התחום של הדף פלוס ערך פורט המוגדר בתבנית הדף ה HTML):

wsInit(`ws://${ location.hostname }:${ window.cfg.wsPort }`);

// לטפל בתקשורת WebSocket
function wsInit(wsServer) {

  const ws = new WebSocket(wsServer);

  // להתחבר לשרת
  ws.addEventListener('open', () => {
    sendMessage('entered the chat room');
  });

אירוע open מופעל כאשר הדפדפן מתחבר לשרת WebSocket. פונקציית המטרה שולחת הודעה נכנס לחדר הצ'אט על ידי קריאה ל-sendMessage():

// לשלוח הודעה
function sendMessage(setMsg) {

  let
    name = dom.name.value.trim(),
    msg =  setMsg || dom.message.value.trim();

  name && msg && ws.send( JSON.stringify({ name, msg }) );

}

פונקציה sendMessage() שואפת את שמו של המשתמש והודעתו מהטופס HTML, אם כי ההודעה יכולה להיות מעולה על ידי כל טיעון מעבר setMsg. הערכים מומרים לאובייקט JSON ונשלחים לשרת WebSocket באמצעות השיטה ws.send().

שרת ה-WebSocket מקבל את ההודעה הנכנסת שמעוררת את מטרת ה-"message" (ראה לעיל) ומפציץ אותו חזרה לכל הדפדפנים. זה מעורר אירוע "message" על כל לקוח:

// לקבל הודעה
ws.addEventListener('message', e => {

  try {

    const
      chat = JSON.parse(e.data),
      name = document.createElement('div'),
      msg  = document.createElement('div');

    name.className = 'name';
    name.textContent = (chat.name || 'unknown');
    dom.chat.appendChild(name);

    msg.className = 'msg';
    msg.textContent = (chat.msg || 'said nothing');
    dom.chat.appendChild(msg).scrollIntoView({ behavior: 'smooth' });

  }
  catch(err) {
    console.log('invalid JSON', err);
  }

});

המטרה מקבלת את הנתונים המועברים בJSON על תכונת ה-.data של אובייקט האירוע. הפונקציה ממירה אותו לאובייקט JavaScript ומעדכנת את חלון הצ'אט.

לבסוף, הודעות חדשות נשלחות באמצעות הפונקציה sendMessage() בכל פעם שמופעל מטרת ה-"submit" של הטופס:

// שליחת טופס
dom.form.addEventListener('submit', e => {
  e.preventDefault();
  sendMessage();
  dom.message.value = '';
  dom.message.focus();
}, false);

להתמודדות עם שגיאות

אירוע "שגיאה" מופעל כאשר תקשורת WebSocket נכשלת. זה יכול להתמודד על השרת:

// לטפל בשגיאת WebSocket בשרת
socket.on('error', e => {
  console.log('WebSocket error:', e);
});

ואו הלקוח:

// לטפל בשגיאת WebSocket בלקוח
ws.addEventListener('error', e => {
  console.log('WebSocket error:', e);
})

רק הלקוח יכול לשחזר את החיבור על ידי הרצת מנהל הבנאי new WebSocket() שוב.

סגירת חיבורים

כל מכשיר יכול לסגור את WebSocket בכל עת באמצעות שימוש בשיטת החיבור .close(). אפשר באופן אופציונלי לספק טיעון קוד שלם וטקסט סיבה (מקסימום 123 בתים), שמועברים למכשיר האחר לפני שהוא נכבה.

WebSocket מתקדמים

ניהול WebSockets קל ב-Node.js: מכשיר אחד שולח הודעה באמצעות שיטת .send(), שמעוררת אירוע "הודעה" על השני. כיצד כל מכשיר יוצר ומגיב להודעות אלו יכול להיות מאתגר יותר. הסעיפים הבאים מתארים בעיות שייתכן שתצטרך לשקול.

אבטחת WebSocket

פרוטוקול WebSocket אינו מטפל בהרשאה או אימות. אין אפשרות להבטיח שבקשת תקשורת חדשה מגיעה מדפדפן או ממשתמש שהתחבר ליישום האינטרנט שלך – במיוחד כאשר השרתים של האינטרנט ו-WebSocket יכולים להיות על מכשירים שונים. החיבור הראשוני מקבל ראוט של HTTP המכיל עוגיות והשרת Origin, אך יש אפשרות לזייף את הערכים אלו.

השיטה הבאה מבטיחה שתצמצם את תקשורת WebSocket למשתמשים המורשים:

  1. לפני שהדפדפן עושה את בקשת WebSocket הראשונה, הוא מתקשר לשרת ה-HTTP (אולי באמצעות Ajax).

  2. השרת בודק את אסמכת המשתמש ומחזיר כרטיס אוטוריזציה חדש. הכרטיס יכול בדר"כ להתייחס לרשומה במסד הנתונים המכילה את מספר המשתמש, כתובת ה-IP, זמן הבקשה, זמן פקירת המזהה, וכל נתונים נוספים הנחוצים.

  3. הדפדפן מעביר את הכרטיס לשרת WebSocket בידידות הידים הראשונה.

  4. שרת WebSocket מאשר את הכרטיס ובודק גורמים כגון כתובת ה-IP, זמן הפקירה וכו' לפני שמאפשר את החיבור. הוא מבצע את שיטת WebSocket .close() כשהכרטיס אינו תקין.

  5. שרת WebSocket עשוי לצפות לבדוק שוב ושוב את רשומת המסד הנתונים כדי לוודא שמזהת המשתמש נשארת תקין.

חשוב לציין, תמיד לאמת את הנתונים הנכנסים:

  • כמו HTTP, שרת WebSocket חשוף להתקפות SQL injection ואחרות.

  • הלקוח לא צריך להזריק ערכים גולמיים ל-DOM או להעריך קוד JavaScript.

הפרדה מול מספר מופעים של שרת WebSocket

חישבו על משחק מרוב שחקנים מקוון. המשחק כולל מספר יקומים של משחקים בודדים: universeA, universeB, ו-universeC. שחקן מתחבר ליקום בודד:

  • universeA: נצטרף player1, player2, ו-player3
  • universeB: נצטרף player99

אפשר ליישם את הבא:

  1. A separate WebSocket server for each universe.

    A player action in universeA would never be seen by those in universeB. However, launching and managing separate server instances could be difficult. Would you stop universeC because it has no players, or continue to manage that resource?

  2. להשתמש בשרת WebSocket יחיד עבור כל יקומי המשחק.

    זה משתמש במשאבים פחות ונוח יותר לניהול, אך שרת ה-WebSocket חייב לתעד לאיזה יקום כל שחקן נצטרף. כש-player1 מבצע פעולה, יש לשדר אותה ל-player2 ו-player3 אך לא ל-player99.

מספר שרתי WebSocket

האפליקציה הדוגמא לצ'אט יכולה להתמודד עם מאות משתמשים קונקרטיים, אך היא תקרוס פעם אחת שהפופולריות ושימוש בזיכרון עולים מעל סף קריטי. בסופו של דבר תצטרך להתקפל אופקית על ידי הוספת שרתים נוספים.

כל שרת WebSocket יכול רק לנהל את הלקוחות המחוברים שלו. הודעה שנשלחה מדפדפן ל-serverX לא יכולה להופצצ לאלו המחוברים ל-serverY. עשוי להיות צורך ליישם מערכות הודעה מוציא-מכונן (pub-sub) במאחז. לדוגמא:

  1. שרת WebSocket serverX רוצה לשלוח הודעה לכל הלקוחות. הוא מפרסם את ההודעה במערכת ה-pub–sub.

  2. כל שרתי WebSocket המנויים למערכת ה-pub–sub מקבלים אירוע של הודעה חדשה (כולל serverX). כל אחד יכול לטפל בהודעה ולשדרגה ללקוחות המחוברים שלהם.

יעילות הודעות WebSocket

תקשורת WebSocket היא מהירה, אך השרת חייב לנהל את כל הלקוחות המחוברים. עליך לשקול את המכניקה והיעילות של ההודעות, במיוחד כאשר אתה בונה משחקי פעולה רבים שחקנים:

  • איך אתה מסדר את הפעולות של השחקן על כל המכשירים הלקוח?

  • אם player1 נמצא במיקום שונה מ player2, האם חייבים לשלוח ל player2 מידע על פעולות שהוא לא יכול לראות?

  • איך אתה מתמודד עם עיכוב הרשת – או עיכוב התקשורת? האם מישהו עם מכונה מהירה וחיבור יהיה ביטחון לא הוגן?

משחקים מהירים חייבים להתפשר. חשוב על זה כאילו אתה משחק את המשחק במכשיר המקומי שלך אך חלק מהאובייקטים מושפעיםמפעילות אחרים. במקום לשלוח את המיקום המדויק של כל אובייקט בכל רגע נתון, משחקים בדרך כלל שולחים הודעות פשוטות ולעתים רחוקות פחות. לדוגמה:

  • objectX הופיע בנקודהX
  • objectY קיבל כיוון ומהירות חדשים
  • objectZ הושמד

כל משחק לקוח משלב את הפערים. כשobjectZ מתפוצץ, לא משנה אם הפיצוץ נראה שונה בכל מכשיר.

מסקנה

Node.js מקל על טיפול ב-WebSockets. זה לא בהכרח מקל על עיצוב או כתיבת יישומים בזמן אמת, אך הטכנולוגיה לא תעכב אותך!

החסרונות העיקריים:

  • WebSockets דורשים מופע שרת נפרד. בקשות Ajax Fetch() ו-אירועים שנשלחו על ידי השרת יכולים להתמודד על ידי השרת האינטרנט שאתה כבר מפעיל.

  • שרתי WebSocket דורשים בדיקות אבטחה והרשאה משלהם.

  • חיבורי WebSocket שנפרקו חייבים להתקבע באופן ידני.

אך אל תיתן לכך להרתיע אותך!

שאלות שכיחות (FAQs) על יישומים בזמן אמת עם WebSockets ואירועים שנשלחו על ידי השרת

איך WebSockets שונים מ-HTTP מבחינת ביצועים ופונקציונליות?

WebSockets מספקים ערוץ תקשורת מלא הדדי מעל קישוריים יחיד של TCP, כלומר ניתן לשלוח ולקבל נתונים בו זמנית. זהו שיפור משמעותי מעל HTTP, שבו כל בקשה דורשת קישור חדש. WebSockets גם מאפשרים העברת נתונים בזמן אמת, מה שהופך אותם לאופטימליים ליישומים שדורשים עדכונים מיידיים, כגון אפליקציות צ'אט או עדכונים בזמן אמת לספורט. לעומת זאת, HTTP היא ללא מצב וכל זוג בקשה-תגובה הוא עצמאי, מה שיכול להיות יותר מתאים ליישומים שבהם אינם דורשים עדכונים בזמט אמת.

האם אפשר להסביר את מחזור החיים של קישור WebSocket?

מחזור החיים של קישור WebSocket מתחיל במפגש ידידותי, שמשדרג קישור HTTP לקישור WebSocket. ברגע שהקישור מושם, ניתן לשלוח ולקבל נתונים בין הלקוח לשרת עד שאחת הצדדים מחליט לסגור את הקישור. הקישור יכול להיסגר על ידי הלקוח או השרת שמשלחים מסר סגירה, לאחר מכן הצד השני מכיר במסר הסגירה.

איך אפשר ליישם WebSockets ביישום אנדרואיד?

יישום WebSockets ביישום אנדרואיד כרוך ביצירת לקוח WebSocket שיכול להתחבר לשרת WebSocket. זה יכול להיעשות באמצעות ספריות כמו OkHttp או Scarlet. ברגע שהלקוח מוגדר, ניתן לפתוח קישור לשרת, לשלוח ולקבל הודעות, ולטפל באירועים שונים כמו פתיחת קישור, קבלת הודעות וסגירת קישור.

מהם מארח-נותן-אירועים ואיך הם משתווים ל-WebSockets?

Server-Sent Events (SSE) הם סטנדרט המאפשר לשרת לשלוח עדכונים ללקוח דרך HTTP. שינויים ב-SSE הם חד-כיווניים, כלומר הם מאפשרים שידור של מידע רק מהשרת ללקוח. זה עושה אותם פחות מתאימים ליישומים שדורשים תקשורת דו-כיוונית, אך הם יכולים להוות פתרון פשוט יותר ויעיל יותר ליישומים שזקוקים רק לעדכונים מהשרת.

מהם מקרים השימוש הנפוצים עבור WebSockets ו-Server-Sent Events?

WebSockets משמשים בדרך כלל ביישומים שדורשים תקשורת דו-כיוונית בזמן אמת, כגון אפליקציות צ'אט, משחקים רבי-שחקן וכלים שיתופיים. Server-Sent Events, לעומתם, משמשים לעתים קרובות ביישומים שזקוקים לעדכונים בזמן אמת מהשרת, כגון עדכוני חדשות חיים, עדכוני מחירי מניות או דיווחים על התקדמות של משימות ארוכות פעולה.

כיצד אוכל לטפל בחיבורי WebSocket ביישום Spring Boot?

Spring Boot מספקת תמיכה בתקשורת WebSocket דרך מודול Spring WebSocket. אפשר להשתמש בעיצוב @EnableWebSocket כדי להפעיל את התמיכה ב-WebSocket, ואז להגדיר WebSocketHandler לטפל במחזור החיים של החיבור ובטיפול בהודעות. אפשר גם להשתמש ב-SimpMessagingTemplate לשליחת הודעות ללקוחות המחוברים.

מהם ההתחשבויות האבטחתיות כשמשתמשים ב-WebSockets?

כמו כל טכנולוגיה אחרת ברשת, WebSockets יכולים להיות פגיעים למגוון סכנות אבטחה, כגון גרימת WebSocket ברשות זרה (CSWSH) והפיכת שירותים לבלתי אפשריים (DoS) מתקפות. כדי לצמצם את הסיכונים הללו, עליכם תמיד להשתמש בחיבורי WebSocket בטוחים (wss://) ולאמת ולסנטרזכן את כל הנתונים הנכנסים. כמו כן, כדאי לשקול להשתמש במנגנוני אימות והרשאה לשליטה בגישה לשרת WebSocket שלכם.

האם אפשר להשתמש ב- WebSockets עם API רשת?

כן, אפשר להשתמש ב- WebSockets בשילוב עם API רשת. בעוד ש- REST APIs מצוינים לתקשורת בקשה-תגובה ללא מצב, WebSockets יכולים לשמש לתקשורת בזמן אמת, דו כיוונית. זה יכול להיות שימושי במיוחד ביישומים שדורשים עדכונים מיידיים, כמו אפליקציות צ'אט או עדכונים בזמן אמת לגבי ספורט.

איך אפשר לבדוק שרת WebSocket?

יש מספר כלים זמינים לבדיקת שרתי WebSocket, כגון בדיקת הדוגמה של WebSocket.org, או Postman. כלים אלה מאפשרים לך לפתוח חיבור WebSocket לשרת, לשלוח הודעות ולקבל תגובות. אתה יכול גם לכתוב בדיקות אוטומטיות עבור שרת WebSocket שלך באמצעות ספריות כמו Jest או Mocha.

מהן המוגבלויות של WebSockets ואירועים שנשלחו על ידי השרת?

בעוד ש- WebSockets ואירועים שנשלחו על ידי השרת מספקים יכולות חזקות לתקשורת בזמן אמת, יש להם גם מוגבלויות. לדוגמה, לא כל הדפדפנים והרשתות תומכות בטכנולוגיות אלה, והן יכולות לצרוך כמות ניכרת של משאבים אם אינם מנוהלים כראוי. כמו כן, הם יכולים להיות מורכבים יותר ביישום ובניהול בהשוואה לתקשורת HTTP מסורתית.

Source:
https://www.sitepoint.com/real-time-apps-websockets-server-sent-events/