• בלוג
  • תקשורת דו-כיוונית עם socket.io

תקשורת דו-כיוונית עם socket.io

24/11/2018

פוסט זה מכיל טיפ קצר על עבודה עם Node.JS. כדי ללמוד Node.JS הרבה יותר לעומק אני ממליץ לכם לבדוק את קורס Node.JS כאן באתר. הקורס כולל עשרות שיעורי וידאו מוקלטים והמון תרגול שילוו אתכם מהצעד הראשון בעבודה עם Node.JS ועד הנושאים המתקדמים.

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

1. מה אנחנו רוצים לבנות

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

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

לקראת שנת 2010 מתכנתים החלו לבנות מנגנונים של מעין תקשורת דו-כיוונית בין השרת ללקוח, כך שיהיה לנו לקוח שכאילו מחובר כל הזמן והשרת יוכל לשלוח הודעות ללקוח באופן יזום (גם בלי בקשת HTTP). בהתחלה השתמשו בבקשת HTTP שנשארה פתוחה לזמן ארוך, מנגנון שנקרא Long Polling, ולאחר מכן עם התפתחויות בשוק הדפדפנים נעשה מעבר למנגנון שנקרא Web Sockets.

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

2. פיתוח שרת Echo ראשון עם Socket.IO

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

בשביל לבנות שרת socket.io נפתח קובץ חדש בתיקיה הראשית של הפרויקט, אני אקרא לו echoserver.js. בתוכו כתבו את התוכן הבא:

const socketio = require('socket.io');

function init(server) {
    const io = socketio(server);
    io.on('connection', function (socket) {
        console.log('new client connected');

        socket.on('echo', function(data) {
            socket.emit('echo', data);
        });
    });
}

module.exports = init;

נראה מה יצרנו כאן:

  1. יצרנו פונקציה שמקבלת משתנה בשם server, שעוד מעט נראה מאיפה מקבלים אותו, ותפקידה לאתחל את socket.io. האיתחול מגדיר רשימה של אירועים שיגיעו מהלקוחות שלנו ואת הפונקציות שיטפלו באירועים אלה. שימו לב שבינתיים זה ממש דומה לעבודה שעשינו באקספרס - רק שהפעם במקום להגדיר נתיבים וקוד טיפול בהם, אנחנו מגדירים אירועים וקוד טיפול בהם.

לכל אירוע ב Socket.IO יכול להתלוות אוביקט מידע. אוביקט המידע יישלח מהלקוח לשרת או מהשרת ללקוח. שרת ה Echo שלנו מאזין לאירוע בשם echo, וכשלקוח ישלח אירוע כזה עם אוביקט מידע השרת ישלח לו בחזרה אירוע חדש באותו שם ועם אותו אוביקט מידע.

  1. נעבור לראות את קוד צד הלקוח המתאים לקוד הזה כדי שתהיה לנו תמונה מלאה על הקשר בין השרת ללקוח ואיך Socket.IO עובד. אחרי זה נוכל להמשיך לראות איך לאתחל את השרת ואת כל הקבצים הנלווים. פתחו בתיקיית public/javascripts קובץ חדש בשם echo.js ובו התוכן הבא:
const socket = io();

$('#inp-msg').on('input', function () {
  const msg = $('#inp-msg').val();
  socket.emit('echo', { msg });
});

socket.on('echo', function(data) {
  const msg = data.msg;
  $('#echo').text(msg);
});

הקובץ מגדיר אוביקט בשם socket שהוא קיבל אותו מהפעלת פונקציה גלובלית בשם io. את הפונקציה הזאת אנחנו מקבלים מקוד ספריית socket.io שייטען גם בצד הלקוח (ונצטרך לטעון אותו בעצמנו מתוך ה HTML).

לאחר מכן ממשיכים להגדיר קוד טיפול ושליחת אירועים. שתי הפונקציות המעניינות כאן הן socket.emit ו socket.on, שתיהן קשורות לתקשורת עם צד השרת. הפונקציה socket.emit שולחת אירוע מהלקוח לשרת. היא מקבלת את שם האירוע ואוביקט מידע. הפונקציה socket.on מחברת קוד טיפול לאירוע שמגיע מהשרת, וגם היא מקבלת את שם האירוע ואת אוביקט המידע.

רצף הפעולות לכן יהיה: גולש כותב טקסט בתיבה שנקראת inp-msg, נוצר אירוע input בדפדפן, הקוד בדפדפן שולף את הטקסט מהתיבה ושולח אותו לשרת באמצעות socket.emit. זמן קצר לאחר מכן השרת מקבל את ההודעה ושולח בחזרה אירוע בשם echo עם אותו אוביקט מידע. אירוע זה מתקבל וגורם להפעלת קוד הטיפול, שמציג את הטקסט שהגיע מהשרת באלמנט אחר על המסך. מבחינה ויזואלית אנחנו הולכים לראות תיבת טקסט שכל טקסט שנכתב בה ישוכפל לאלמנט נוסף - שזה ממש לא כזה מלהיב, אבל מתחת לפני השטח כדאי לזכור שיש כאן שליחת מידע מהדפדפן לשרת ובחזרה וזה משהו שנוכל בשיעורים הבאים להרחיב מאוד בקלות.

  1. נמשיך לשינויים בקובץ bin/www שיגרמו לפרויקט לעבוד: אנחנו צריכים לטעון את קובץ ה echoserver.js שכתבנו ולהפעיל את פונקציית האיתחול משם. הוסיפו לקובץ את השורות הבאות:
var socketserver = require('../echoserver');
socketserver(server);

אפשר בסוף הקובץ או בכל מקום אחרי איתחול אוביקט ה server שכבר מוגדר שם.

  1. לסיום נרצה לכתוב קובץ HTML שיטען את ה JavaScript שלנו ויכיל את כל האלמנטים הויזואליים שאנחנו צריכים על המסך. הפעם זה לא יהיה view אלא פשוט HTML סטטי לגמרי. פתחו קובץ בתיקיית public בשם echo.html ובו התוכן הבא:
<!DOCTYPE html>
<html>

<head></head>

<body>
    <label>
        Type some text: 
        <input type="text" id="inp-msg" />
    </label>
    <p>Server said: <span id="echo"></span></p>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"
    integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
    crossorigin="anonymous"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script src="/javascripts/echo.js"></script>
</body>

</html>

הפעילו את השרת, כנסו לכתובת echo.html ושחקו עם התיבות כדי לראות את הקוד בפעולה.

3. רשימת אירועים שמורים

הפונקציות socket.on ו socket.emit מאפשרות לנו לבחור שם לאירוע. כדאי לשים לב שחלק מהשמות כבר תפוסים על ידי socket.io עצמה ואם תשתמשו באחד השמות השמורים האירועים לא יעברו וגם לא יופיעו הודעות שגיאה. אלה השמות שאסור לכם להשתמש בהם בתוכנית לאירועים שלכם:

  1. error
  2. connect
  3. disconnect
  4. disconnecting
  5. newListener
  6. removeListener
  7. ping
  8. pong

4. מה הלאה?

רוצים לקחת את הדוגמא צעד אחד קדימה? נסו להפוך את שרת ה echo לשרת צ'ט. מסתבר שזה די פשוט- הפונקציה socket.broadcast.emit שולחת הודעה לכל הלקוחות מלבד זה ששלח לנו את ההודעה. לכן אם נחליף את הקוד בצד השרת לקוד הבא:

function init(server) {
    const io = socketio(server);
    io.on('connection', function (socket) {
        console.log('new client connected');

        socket.on('msg', function(data) {
            socket.broadcast.emit('msg', data);
        });
    });
}

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

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