پیاده‌سازی اپلیکیشن real-time با node و react و socket.io

ترجمه و تالیف : عرفان حشمتی
تاریخ انتشار : 22 مهر 99
خواندن در 5 دقیقه
دسته بندی ها : نود جی اس

در این مقاله با برخی از مفاهیم اساسی شروع خواهیم کرد تا از طریق کاوش آنچه Socket.IO و React هنگامی که با هم جفت شوند، می‌توانند برای ما انجام دهند. در پایان مقاله شما یک برنامه فوق‌العاده ساده real-time خواهید ساخت.

این مقاله کمی طولانی خواهد شد. قبل از شروع یک فنجان چای آماده کنید و با ما همراه باشید!

آنچه یاد خواهید گرفت

WebSocket چگونه از Socket.IO و Node.js در کنار ری‌اکت استفاده می‌کند.

نیازمندی‌ها

برای پیگیری این آموزش شما باید یک درک اساسی از جاوااسکریپت، node.js و ExpressJS داشته باشید. همچنین حتما آخرین نسخه node.js را دریافت کنید.

نکته آخر اینکه اگر هنوز یک کلید API از DarkSky دریافت نکرده‌اید، یکی را تهیه کنید. ما بعدا در برنامه ExpressJS خود از DarkSky API استفاده خواهیم کرد.

پروتکل‌های WebSocket، Node.js و Socket.IO

WebSocket یک پروتکل ارتباطی اینترنتی با یک ویژگی جالب مرتبط است. این یک کانال دو طرفه کامل را از طریق یک اتصال TCP ارائه می‌دهد.

با WebSocket، یک سرویس گیرنده و یک سرور می‌توانند به صورت real-time با یکدیگر صحبت کنند، مانند اینکه در یک تماس تلفنی درگیر شده‌اند. پس از اتصال، کلاینت می‌تواند داده‌ها را از سرور دریافت کند، بدون نیاز به رفرش مداوم صفحه وب. از طرف دیگر سرور همچنین می‌تواند داده‌ها را به صورت real-time از کاربر در همان اتصال دریافت کند.

آنچه جالب‌تر است توانایی WebSocket برای کار با یک مدل مبتنی بر رویداد است. سرور و کلاینت می‌توانند در برابر رویدادها و پیام‌ها واکنش نشان دهند.

WebSocket‌ها دنیای کاملی از فرصت‌ها را برای توسعه دهندگان وب ایجاد کرده‌اند. اگر می‌خواهید بدانید این فناوری خارق‌العاده را چگونه در برنامه‌های node.js خود پیاده کنید، پاسخ Socket.IO است که یکی از محبوب‌ترین موتورهای node.js در real-time است.

Socket.IO با استفاده از رویدادهای node.js کار می‌کند. شما می‌توانید به یک رویداد اتصال گوش دهید، هنگامی که کاربر جدید به سرور متصل می‌شود یک عملکرد را فعال کنید، از طریق سوکت یک پیام (اساساً یک رویداد) منتشر کنید و موارد دیگر.

Socket.IO توسط شرکت‌ها و توسعه دهندگان بی شماری مورد استفاده قرار می‌گیرد و راه خود را از طریق برنامه‌های پیام رسان فوری پیدا کرده است. همچنین برای جریان و همکاری اسناد نیز استفاده می‌شود.

اما یک نکته که باید به خاطر داشته باشید این است که Socket.IO یک پیاده‌سازی WebSocket نیست. نویسندگان اظهار داشتند که Socket.IO در واقع در صورت امکان از WebSocket به عنوان حمل و نقل استفاده می‌کند اما سرویس گیرنده WebSocket قادر به اتصال به یک سرور Socket.IO نخواهد بود که به یک سرور WebSocket متصل شود.

علاوه بر این فریمورک دقیقا مانند WebSocket رفتار می‌کند و در اینجا قدرت آن نهفته است. با استفاده از این موضوع و با درک اولیه از پروتکل WebSocket، وقت آن است که آستین خود را بالا بزنیم.

آماده سازی پروژه

برای شروع یک دایرکتوری خالی به نام socket-io-server ایجاد کرده و وارد آن شوید:

mkdir socket-io-server && cd $_

سپس با اجرای pack.json مقداردهی اولیه را انجام دهید:

npm init -y

ما هیچ ماژولی را در NPM منتشر نمی‌کنیم تا بتوانید با خیال راحت گزینه‌های پیش‌فرض را بپذیرید و فقط ادامه دهید.

ما همچنین باید Socket.io را نصب کنیم که وابستگی اصلی پروژه ما، ExpressJS و Axios است. Express به ما در ساخت سرور کمک خواهد کرد و از Axios برای درخواست HTTP به DarkSky API استفاده خواهد شد:

npm i axios express socket.io

بنابراین ایده پشت پروژه کوچک ما ساده است: کتی می‌خواهد از دمای فعلی فلورانس مطلع شود و همچنین ممکن است هر 10 ثانیه به روزرسانی شود.

ممکن است وسوسه شوید که یک فراخوانی با DarkSky را در داخل متد componentDidMount از یک کامپوننت ری‌اکت قرار دهید. شاید شما باید هر 10 ثانیه با فراخوانی setInterval مستقیما درون کامپوننت DidMount از API نظرسنجی کنید. خوشبختانه روش‌های بهتری وجود دارد. برای پروژه ما یک سرور ساده در real-time کار را به پایان می‌رساند.

سرور از Socket.IO برای انتشار هر 10 ثانیه یک پیام استفاده می‌کند و کلاینت همان پیام را از طریق یک سوکت در real-time گوش می‌دهد. شسته و رفته به نظر می‌رسد؟ بله اینطوراست. آیا من می‌توانم ری‌اکت را ترکیب کنم وقتی که می‌توانم HTML را به راحتی با Pug یا Jade انجام دهم؟ بله، بسیار جالب است که ببینید ری‌اکت چگونه می‌تواند در کنار Socket.IO کار کند.

به زودی خواهید دید فایلی را با نام app.js در داخل فهرست پروژه خود ایجاد می‌کنیم. این سرور واقعی را نگه می‌دارد:

const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const axios = require("axios");
const port = process.env.PORT || 4001;
const index = require("./routes/index");
const app = express();
app.use(index);
const server = http.createServer(app);
const io = socketIo(server); // < Interesting!
const getApiAndEmit = "TODO"

کد بالا نباید برای شما گنگ و مبهم باشد. این مجموعه‌ای از نیازها است که به دنبال آن فراخوانی یک برنامه جدید ExpressJS انجام می‌شود. آنچه که در اینجا بسیار جالب است، فراخوانی socketIo() برای شروع یک نمونه جدید با عبور از شی سرور است. با این کار سرور ExpressJS را به Socket.IO متصل کرده ایم.

شما همچنین باید یک تابع خالی را مشاهده کنید:

const getApiAndEmit = "TODO"

در مرحله بعدی آن را با چند کد معنی‌دار پر خواهیم کرد.

همچنین به نحوه فراخوانی برنامه از مسیر index توجه کنید. حتی اگر سرور هیچ محتوای HTML را ارائه ندهد، برای گوش دادن به هرگونه اتصال ورودی به یک مسیر بسیار ساده نیاز خواهیم داشت.

فایلی به نام index.js در داخل فهرست مسیرها ایجاد کنید:

const express = require("express");
const router = express.Router();
router.get("/", (req, res) => {
  res.send({ response: "I am alive" }).status(200);
});
module.exports = router;

و تمام.

طراحی سرور

اولین و مهم‌ترین متدی که در حین کار با Socket.IO با آن روبه رو خواهید شد، متد ()on است که دو آرگومان می‌گیرد: نام رویداد، در این حالت "اتصال" و پاسخگویی که پس از هر رویداد اتصال اجرا می‌شود. ()on چیزی بیش از یک متد هسته node.js نیست که به کلاس EventEmitter گره خورده است.

رویداد اتصال یک شی سوکت را برمی‌گرداند که به تابع برگشت تماس منتقل می‌شود. با استفاده از سوکت گفته شده می‌توانید داده‌ها را در real-time برای کلاینت ارسال کنید.

اگر به یاد داشته باشید، کتی می‌خواهد هر 10 ثانیه دما را بشناسد. می‌توانیم در داخل callback از setInterval استفاده کنیم و در داخل setInterval می توان از تابع فلش دیگری استفاده کرد که عملکرد getApiAndEmit را که قبلا دیدیم فراخوانی کند. کد باید بسیار ساده است:

io.on("connection", socket => {
  console.log("New client connected"), setInterval(
    () => getApiAndEmit(socket),
    10000
  );
  socket.on("disconnect", () => console.log("Client disconnected"));
});

همچنین توجه کنید که چگونه می‌توانیم به رویداد قطع ارتباط گوش کنیم. به محض اینکه سرویس گیرنده ارتباط خود را از سرور قطع کند، این سیستم خارج می‌شود. در حال حاضر ما فقط یک پیام ساده روی کنسول نمایش خواهیم داد.

نکته مهم: همانطور که توسط برخی دیگر از خوانندگان اشاره شده است، کد بالا دارای یک نقص است: برای هر کلاینت متصل یک بازه جدید ایجاد می‌شود. در حالی که Socket.IO برای اداره بسیاری از اتصالات همزمان به وجود آمد. مثال ما فرض می‌کند که فقط یک کاربر از صفحه بازدید خواهد کرد. اگر قرار باشد کد را در پروژه اصلیتان قرار دهید، پیشنهاد می‌شود این کار را نکنید.

نسخه جدی تری از قطعه کد فوق اتصالات بعدی را پاک می‌کند:

let interval;
io.on("connection", socket => {
  console.log("New client connected");
  if (interval) {
    clearInterval(interval);
  }
  interval = setInterval(() => getApiAndEmit(socket), 10000);
  socket.on("disconnect", () => {
    console.log("Client disconnected");
  });
});

به هر حال برای پاک کردن شناسه فاصله، کاملا خوب است.

پیاده‌سازی سرور

اکنون می‌توانیم برنامه را برای ارتباطات ورودی گوش دهیم:

server.listen(port, () => console.log(`Listening on port ${port}`));

آیا تابع getApiAndEmit را به خاطر می‌آورید؟ سوکت را به عنوان یک آرگومان می‌گیرد. سوکت چیزی بیش از کانال ارتباطی بین سرویس گیرنده و سرور نیست. ما می‌توانیم با انتشار یک پیام هر آنچه را که بخواهیم در داخل آن بنویسیم:

const getApiAndEmit = async socket => {
  try {
    const res = await axios.get(
      "https://api.darksky.net/forecast/PUT_YOUR_API_KEY_HERE/43.7695,11.2558"
    ); // Getting the data from DarkSky
    socket.emit("FromAPI", res.data.currently.temperature); // Emitting a new message. It will be consumed by the client
  } catch (error) {
    console.error(`Error: ${error.code}`);
  }
};

این تابع سوکت را به عنوان آرگومان می‌گیرد، درخواست HTTP را به DarkSky API می‌دهد (فراموش نکنید که آدرس اینترنتی را با کلید API واقعی خود پر کنید) و در آخر پیام "FromAPI" را که حاوی مقدار دمای فعلی برای آن است، نشان می‌دهد.

پیام منتشر شده توسط کلاینت Socket.IO قابل رهگیری است.

از نظر سرور کار ما تمام شده است و کد کامل app.js باید به این شکل باشد.

با شروع برنامه می‌توانیم سرور خود را آزمایش کنیم:

node app.js

به محض شروع سرور، خروجی زیر را مشاهده خواهید کرد. "گوش دادن به پورت 4001" که تأیید می‌کند همه چیز خوب کار می‌کند.

پیاده‌سازی کلاینت با ری‌اکت

اکنون که سرور کوچک واقعی را در پروژه خود قرار داده‌ایم، وقت آن است که داده‌های خود را تست کنیم. ما می‌خواهیم دمای فعلی را در فلورانس نمایش دهیم و برای انجام کار از ری‌اکت استفاده خواهیم کرد.

چرا؟ زیرا ری‌اکت دقیقا همان کاری را انجام می‌دهد که از نامش پیداست. در برابر تغییرات وضعیت واکنش نشان می‌دهد. سرور ما پیامی حاوی دمای فعلی را منتشر می‌کند که هر 10 ثانیه به روز می‌شود.

ری‌اکت می‌تواند مقدار دما را در داخل کامپوننت ذخیره کند و فقط بخشی از متن را در معرض تغییرات قرار دهد. اگر اولین بار است که با ری‌اکت کار می‌کنید، با استفاده از ابزارهای ساختاری وقت را تلف نکنید و از create-react-app استفاده کنید:

npx create-react-app socket-io-client

(توجه داشته باشید که شما باید پروژه را خارج از پوشه محل سرور ایجاد کنید). سپس به داخل پروژه بروید و کلاینت Socket.IO را نصب کنید:

npm i socket.io-client

برای ساده نگه داشتن موارد ما فقط از کامپوننت App.js استفاده می‌کنیم که در داخل دایرکتوری src قرار دارد. App.js را باز کنید، می‌توانید با خیال راحت تمام محتوای داخل فایل را حذف کرده و کد را با موارد زیر جایگزین کنید:

import React, { Component } from "react";
import socketIOClient from "socket.io-client";
class App extends Component {
  constructor() {
    super();
    this.state = {
      response: false,
      endpoint: "http://127.0.0.1:4001"
    };
  }
  componentDidMount() {
    const { endpoint } = this.state;
    const socket = socketIOClient(endpoint);
    socket.on("FromAPI", data => this.setState({ response: data }));
  }
  render() {
    const { response } = this.state;
    return (
        <div style={{ textAlign: "center" }}>
          {response
              ? <p>
                The temperature in Florence is: {response} °F
              </p>
              : <p>Loading...</p>}
        </div>
    );
  }
}
export default App;

اکنون فایل را ذخیره کرده و ببندید، بعد مستقیما به قسمت بعدی بروید.

ادغام کردن فرانت‌اند و بک‌اند

اکنون یک ترمینال را باز کنید، وارد پوشه سرور شوید و Socket.io را شروع کنید:

cd socket-io-server
node app.js

در ترمینال دیگری وارد پوشه کلاینت شوید و پروژه ری‌اکت را شروع کنید:

cd socket-io-client
npm start

10 ثانیه صبر کنید و باید خروجی زیر را ببینید (من استایل دیگری را به کامپوننت خود اضافه کرده‌ام، لطفا CSS را نیز به App.js خود اضافه کنید):

اگر به صفحه توجه داشته باشید، هر 10 ثانیه یکبار متوجه تغییر دما می‌شوید. این جادوی Socket.IO است: به محض نصب کامپوننت ری‌اکت، componentDidMount با ایجاد سوکت جدید اتصال جدیدی به سرور Socket.IO ما ایجاد می‌کند.

به یاد داشته باشید، سوکت یک کانال ارتباطی است و ما می‌توانیم به هر رویدادی که در آن اتفاق می‌افتد گوش دهیم. اگر نگاهی به کد سمت سرور بیندازید، پیام/رویداد "FromAPI" به محض اتصال کلاینت جدید به سرور، خاموش می‌شود.

کلاینت می‌تواند برای رویداد با روش ()on گوش کند و با داده‌های موجود در پیام/رویداد کاری انجام دهد. در پروژه ما، به سادگی می‌خواهیم دما را در استیت کامپوننت خود ذخیره کنیم. پس از برقراری ارتباط، بدون نیاز به رفرش کردن صفحه، به روزرسانی‌ها از سرور دریافت می‌شود.

از سال 2019 دیگر نیازی به استفاده از کلاس‌های ES6 برای ذخیره استیت یک کامپوننت نیست. با React Hooks حتی یک تابع نیز انجام می‌شود.

بعد از این چه کار کنیم

حتی اگر مثال ما کاملا اساسی باشد، باید مشخص شود که Socket.IO نه تنها برای پیام‌رسانی فوری بلکه برای اهداف وسیعی نیز مناسب است. محدودیت فقط در خلاقیت ماست.

من پیشنهاد می‌کنم مستندات Socket.IO را جستجو کنید تا در مورد اتاق‌ها، فضاهای نام و سایر متدهای API بیشتر بدانید. همچنین درک خوبی از معماری رویداد محور Node.js برای تسلط بر Socket.IO مفید خواهد بود.

اگر هرگونه نظر، سوال یا پیشنهادی دارید، در صورت تمایل آن را در بخش نظرات زیر قرار دهید.

منبع

گردآوری و تالیف عرفان حشمتی
آفلاین
user-avatar

عرفان حشمتی هستم، مهندس سخت افزار و برنامه نویس و طراح وب سایت، علاقه مند به دنیای آی تی و تکنولوژی، همچنین در حوزه ادیت فیلم و تصویر مطالعه و تمرین می کنم.

دیدگاه‌ها و پرسش‌ها

برای ارسال نظر لازم است ابتدا وارد سایت شوید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید