آموزش Socket.io


۶ مرداد ۱۳۹۹
آموزش socket.io

چندین راه برای ایجاد برنامه‌های ارتباطی real-time وجود دارد. دو مورد از شناخته شده‌ترین راه‌ها برای این کار، استفاده از HTTP Long Polling و یا WebSocketها است. در این مقاله خلاصه‌ای از مقایسه میان این دو را مطالعه خواهید کرد. اما تصمیم گرفتیم که در این مقاله بر روی WebSocketها تمرکز کنیم. به این دلیل این مقاله آموزش Socket.io برای ساخت یک برنامه چت real-time است.

WebSockets چیست؟

WebSockets API یک تکنولوژی است که یک کانال دو طرفه برای ارتباط میان کلاینت و سرور را فراهم می‌کند. این بدان معنی است که دیگر لازم نیست کلاینت، به هنگام درخواست دیتا از سرور، آغاز کننده transaction باشد.

به هنگام ایجاد اولین درخواست به سمت سرور، به غیر از دریافت دیتا، یک ارتباط WebSocket ایجاد می‌کند. این فرآیند، تحت عنوان WebSocket handshake نیز شناخته می‌شود. هدر Upgrade نیز در درخواست وجود دارد. این روشی است که کلاینت به سرور اطلاع می‌دهد که نیاز دارد اتصالی را ایجاد کند.

WebSockets در مقابل HTTP

بنابراین، آیا این بدان معنی است که ایجاد برنامه‌های چت و گفت‌وگو، بدون WebSockets غیرممکن است؟

بسیار خب، تکنیکی تحت عنوان HTTP Long Polling وجود دارد. با استفاده از این، کلاینت درخواستی را به سمت سرور ارسال می‌کند و سرور این اتصال را تا زمانی که دیتا جدیدی در دسترس باشد، باز نگه می‌دارد. به هنگامی که دیتا در دسترس قرار بگیرد، کلاینت آن را دریافت می‌کند، سپس درخواست جدید ایجاد و ارسال می‌شود، این عملیات بار‌ها و بار‌ها تکرار می‌شود. به‌هر‌حال، یک عیب بزرگ در استفاده از HTTP Long Polling، استفاده زیاد از منابع سرور است.

ایجاد یک برنامه چت توسط Socket.io

در ادامه به طور خلاصه به شما آموزش می‌دهیم که چگونه توسط Socket.io، به همراه Vanilla JS برای فرانت‌اند و NodeJS به عنوان سرور، یک برنامه چت ساده بسازید. ابتدا نیاز دارید که یک پروژه به همراه فایل package.json ایجاد کنید:

ایجاد یک پروژه توسط دستور npm

بعد از ایجاد پروژه، نیاز است که سرور را راه‌اندازی و تمام وابستگی‌ها را نصب کنید. برای انجام این کار، ابتدا فایل index.js را ایجاد و socket.io و express را نصب کنید. برای این کار، می‌توانید از دستور زیر استفاده کنید:

touch index.js && npm install express socket.io && npm install --save-dev nodemon

در فایل index.js، نیاز است که سرور لوکال را راه‌اندازی و تنظیمات پایه‌ای برای اتصال سوکت‌ها را ایجاد کنید. اساسا در این لحظه، این تمام کاری است که باید در سمت بک‌اند انجام دهید. به هنگامی که یک درخواست مناسب ایجاد کنید، باید اتصال ایجاد شود. این موضوع توسط logیی که توسط برنامه ایجاد می‌شود، قابل اثبات خواهد بود:

const express = require("express");
const socket = require("socket.io");

// App setup
const PORT = 5000;
const app = express();
const server = app.listen(PORT, function () {
  console.log(`Listening on port ${PORT}`);
  console.log(`http://localhost:${PORT}`);
});

// Static files
app.use(express.static("public"));

// Socket setup
const io = socket(server);

io.on("connection", function (socket) {
  console.log("Made socket connection");
});

حالا نیاز است که سمت فرانت‌اند را آماده کنید. ابتدا با ایجاد پوشه public به همراه فایل‌های index.html و main.js، این کار را شروع می‌کنیم. اگر نیاز داشته باشید، می‌توانید فایل style.css را نیز ایجاد کنید.

در زیر، ساختار و محتویات فایل index.html را مشاهده خواهید کرد. اساسا تمام نیاز شما، چندین تگ HTML به همراه یک سری اسکریپت از Socket.io، خواهد بود. فایل index.html شما، به همراه اسکریپت‌های WebSockets، باید چیزی شبیه کد زیر باشد:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Socket.io simple chat</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div class="container">
      <div class="inbox">
        <div class="inbox__people">
          <h4>Active users</h4>
        </div>
        <div class="inbox__messages">
          <div class="messages__history"></div>
          <div class="fallback"></div>
        </div>
      </div>

      <form class="message_form">
        <input type="text" class="message_form__input" placeholder="Type a message" />
        <button class="message_form__button" type="submit">
          Enter
        </button>
      </form>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
    <script src="main.js"></script>
  </body>
</html>

سپس نیاز است که اتصال را در سمت فرانت‌اند راه‌اندازی کنید. تنها خط کدی که در فایل main.js نیاز دارید، const socket = io() است. در ویدیو زیر این موضوع را مشاهده خواهید کرد:

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

در سوکت، بعد از اتصال کاربر، دو راه برای ایجاد eventها وجود دارد. راه اول از سمت کاربر به سمت دیگران (حتی خود کاربر) است و دیگری event را در سایر instanceها ایجاد می‌کند. ایده در اینجا، نمایش کاربران فعال است. بنابراین هنگامی که یک کاربر متصل می‌شود، آن‌ها باید در رابطه با آن اطلاعاتی کسب و لیست کاربران فعال در آن لحظه را دریافت کنند.

const activeUsers = new Set();

io.on("connection", function (socket) {
  console.log("Made socket connection");

  socket.on("new user", function (data) {
    socket.userId = data;
    activeUsers.add(data);
    io.emit("new user", [...activeUsers]);
  });

  socket.on("disconnect", () => {
    activeUsers.delete(socket.userId);
    io.emit("user disconnected", socket.userId);
  });
});

هنگامی که یک کاربر متصل می‌شود، این اتصال، eventیی را اجرا می‌کند که شامل اطلاعاتی در رابطه با نام‌کاربری آن‌ها می‌شود. باید userId را در سوکت، تنظیم کنید، این مورد به هنگام پایان اتصال کاربر مورد نیاز خواهد بود. برای انجام آن نیاز است که نام‌کاربری را به یک Set که شامل نام‌کاربری کاربران فعال است، اضافه کنید و در آخر event را به همراه لیستی از کاربران فعال، صدا بزنید.

در فایل main.js، دو تابع و دو socket listener ایجاد کرده‌ایم:

const socket = io();

const inboxPeople = document.querySelector(".inbox__people");

let userName = "";

const newUserConnected = (user) => {
  userName = user || `User${Math.floor(Math.random() * 1000000)}`;
  socket.emit("new user", userName);
  addToUsersBox(userName);
};

const addToUsersBox = (userName) => {
  if (!!document.querySelector(`.${userName}-userlist`)) {
    return;
  }

  const userBox = `
    <div class="chat_ib ${userName}-userlist">
      <h5>${userName}</h5>
    </div>
  `;
  inboxPeople.innerHTML += userBox;
};

// new user is created so we generate nickname and emit event
newUserConnected();

socket.on("new user", function (data) {
  data.map((user) => addToUsersBox(user));
});

socket.on("user disconnected", function (userName) {
  document.querySelector(`.${userName}-userlist`).remove();
});

ابتدا یک کاربر جدید ایجاد کردیم (به سادگی می‌توانید کارایی آن را با افزودن چندین ورودی از نام کاربران واقعی، گسترش دهید) و event را به همراه آن نام‌کاربری اجرا کردیم. هنگامی که اتصال یک کاربر در سمت سرور قطع می‌شود، نام‌کاربری آن از لیست کاربران فعال حذف و event مربوط به قطع اتصال یک کاربر به همراه نام آن کاربر، اجرا خواهد شد. بعد از آن، از لیست کاربران در سمت کلاینت حذف خواهد شد. در ویدیو زیر می‌توانید این مورد را مشاهده کنید:

بعد از حل مسئله اتصال و قطع‌شدن کاربران، نوبت به مدیریت پیام‌های جدید می‌رسد. در سمت سرور، کاری به جز افزودن listener زیر، نیاز ندارید:

socket.on("chat message", function (data) {
    io.emit("chat message", data);
});

در سمت کلاینت نیاز است موارد زیر را اضافه کنید:

const inputField = document.querySelector(".message_form__input");
const messageForm = document.querySelector(".message_form");
const messageBox = document.querySelector(".messages__history");

const addNewMessage = ({ user, message }) => {
  const time = new Date();
  const formattedTime = time.toLocaleString("en-US", { hour: "numeric", minute: "numeric" });

  const receivedMsg = `
  <div class="incoming__message">
    <div class="received__message">
      <p>${message}</p>
      <div class="message__info">
        <span class="message__author">${user}</span>
        <span class="time_date">${formattedTime}</span>
      </div>
    </div>
  </div>`;

  const myMsg = `
  <div class="outgoing__message">
    <div class="sent__message">
      <p>${message}</p>
      <div class="message__info">
        <span class="time_date">${formattedTime}</span>
      </div>
    </div>
  </div>`;

  messageBox.innerHTML += user === userName ? myMsg : receivedMsg;
};

messageForm.addEventListener("submit", (e) => {
  e.preventDefault();
  if (!inputField.value) {
    return;
  }

  socket.emit("chat message", {
    message: inputField.value,
    nick: userName,
  });

  inputField.value = "";
});

socket.on("chat message", function (data) {
  addNewMessage({ user: data.nick, message: data.message });
});

تابع مسئول نمایش پیام جدید، بلافاصله بعد از دریافت اطلاعات از سوکت است. پیام توسط فرم، به سمت listener موجود در سمت سرور ارسال می‌شود. تنها نیاز است که چک کنید کلاینت نویسنده پیام است یا نه.

ارسال و دریافت پیام در سمت کلاینت

اکنون به شما چگونگی اجرای eventهای مسئول برای سایر اتصالات، به غیر از اتصال خودتان را نمایش خواهیم داد. می‌خواهیم نشان دهیم که در این لحظه، یک کاربر در حال نوشتن چیزی است یا خیر. در سمت سرور، نیاز دارید تا socket.broadcast.emit را اضافه کنید. به کد زیر، که کد نهایی است، نگاهی بیندازید:

const express = require("express");
const socket = require("socket.io");

// App setup
const PORT = 5000;
const app = express();
const server = app.listen(PORT, function () {
  console.log(`Listening on port ${PORT}`);
  console.log(`http://localhost:${PORT}`);
});

// Static files
app.use(express.static("public"));

// Socket setup
const io = socket(server);

const activeUsers = new Set();

io.on("connection", function (socket) {
  console.log("Made socket connection");

  socket.on("new user", function (data) {
    socket.userId = data;
    activeUsers.add(data);
    io.emit("new user", [...activeUsers]);
  });

  socket.on("disconnect", () => {
    activeUsers.delete(socket.userId);
    io.emit("user disconnected", socket.userId);
  });

  socket.on("chat message", function (data) {
    io.emit("chat message", data);
  });
  
  socket.on("typing", function (data) {
    socket.broadcast.emit("typing", data);
  });
});

در سمت کلاینت، باید به event مرتبط به فشردن کلید برای inputField و socket listener برای typing، نگاهی بیندازید. در زیر هر دو مورد را در نسخه نهایی فایل main.js مشاهده می‌کنید:

const socket = io();

const inboxPeople = document.querySelector(".inbox__people");
const inputField = document.querySelector(".message_form__input");
const messageForm = document.querySelector(".message_form");
const messageBox = document.querySelector(".messages__history");
const fallback = document.querySelector(".fallback");

let userName = "";

const newUserConnected = (user) => {
  userName = user || `User${Math.floor(Math.random() * 1000000)}`;
  socket.emit("new user", userName);
  addToUsersBox(userName);
};

const addToUsersBox = (userName) => {
  if (!!document.querySelector(`.${userName}-userlist`)) {
    return;
  }

  const userBox = `
    <div class="chat_ib ${userName}-userlist">
      <h5>${userName}</h5>
    </div>
  `;
  inboxPeople.innerHTML += userBox;
};

const addNewMessage = ({ user, message }) => {
  const time = new Date();
  const formattedTime = time.toLocaleString("en-US", { hour: "numeric", minute: "numeric" });

  const receivedMsg = `
  <div class="incoming__message">
    <div class="received__message">
      <p>${message}</p>
      <div class="message__info">
        <span class="message__author">${user}</span>
        <span class="time_date">${formattedTime}</span>
      </div>
    </div>
  </div>`;

  const myMsg = `
  <div class="outgoing__message">
    <div class="sent__message">
      <p>${message}</p>
      <div class="message__info">
        <span class="time_date">${formattedTime}</span>
      </div>
    </div>
  </div>`;

  messageBox.innerHTML += user === userName ? myMsg : receivedMsg;
};

// new user is created so we generate nickname and emit event
newUserConnected();

messageForm.addEventListener("submit", (e) => {
  e.preventDefault();
  if (!inputField.value) {
    return;
  }

  socket.emit("chat message", {
    message: inputField.value,
    nick: userName,
  });

  inputField.value = "";
});

inputField.addEventListener("keyup", () => {
  socket.emit("typing", {
    isTyping: inputField.value.length > 0,
    nick: userName,
  });
});

socket.on("new user", function (data) {
  data.map((user) => addToUsersBox(user));
});

socket.on("user disconnected", function (userName) {
  document.querySelector(`.${userName}-userlist`).remove();
});

socket.on("chat message", function (data) {
  addNewMessage({ user: data.nick, message: data.message });
});


socket.on("typing", function (data) {
  const { isTyping, nick } = data;

  if (!isTyping) {
    fallback.innerHTML = "";
    return;
  }

  fallback.innerHTML = `<p>${nick} is typing...</p>`;
});

عملکرد این مورد را می‌توانید در ویدیو زیر مشاهده کنید:

جمع‌بندی

امیدواریم این آموزش به شما در فهم سادگی استفاده از Socket.io کمکی کرده باشد، همچنین توسط دانشی پایه‌ای در رابطه با جاوااسکریپت و NodeJS می‌توانید کارهای جذاب زیادی انجام دهید. لیست زیر، برنامه‌هایی هستند که می‌توان توسط WebSockets، آن‌ها را ایجاد کرد:

  • برنامه‌ای برای استریم مسابقات تیم ورزشی محل‌تان. می‌توانید بازی‌ها و یا ویدیوها یا حتی هر دو را استریم کنید.
  • بازی چندنفره برای شما و دوستان‌تان.
  • برنامه چت محرمانه برای گروه‌تان.
  • برنامه‌های براساس GPS، شاید بازی‌هایی که از لوکیشن کاربر استفاده می‌کنند.

به هنگام استفاده از Socket.io، تنها محدودیت، تصورات و ابتکارات شما است.

منبع: https://tsh.io/blog/socket-io-tutorial-real-time-communication

برچسب‌ها:

خدمات رایگان لیارا

۲.۵ گیگابایت فضای ذخیره‌سازی ابری رایگان

۲.۵ گیگابایت Object Storage سازگار با پروتکل S3 با دیسک‌های SSD به‌صورت رایگان دریافت کنید.

هاست رایگان برای دیتابیس‌

دیتابیس‌های MariaDB، PostgreSQL و Redis را فقط با یک کلیک و به‌صورت رایگان تهیه کنید.

سرویس DNS رایگان

به سادگی دامنه‌تان را اضافه کنید و به صورت رایگان رکورد‌های آن را مدیریت کنید.

۱۰۰ هزار تومان اعتبار اولیه

بعد از ثبت نام در لیارا مبلغ ۱۰۰ هزار تومان اعتبار هدیه دریافت می‌کنید که با توجه به ساعتی بودن هزینه سرویس‌ها، می‌توانید تمامی خدمات پولی را برای چندین هفته رایگان استفاده کنید.

ارسال ۱۰۰ ایمیل تراکنشی رایگان در هر ماه

در سرویس ایمیل لیارا شما می‌توانید تا ۱۰۰ ایمیل رایگان در هر ماه ارسال کنید و فقط برای بیش از آن هزینه پرداخت کنید. (به‌همراه دسترسی SMTP)

هاست رایگان برای انواع وبسایت

تفاوتی ندارد برای وبسایت خود از Node استفاده می‌کنید یا Laravel و Django، در لیارا می‌توانید به صورت کاملا رایگان آن را میزبانی کنید.

توسعه‌دهندگان درباره‌ی ما چه می‌گویند

تجربه کار باliara_cloud@امروز خیلی خوب بود. یکی از سرویس هام رو منتقل کردم روش و راضیم. انقد سریع و جذاب کارم راه افتادم اصن باورم نمیشد! برعکس سرویس های PaaS دیگه با اون همه پیچیدگیشون. دمتون گرم
...

MohammadReza
liara testimonial
keikaavousi

بعد از بسته شدن @fandoghpaas و ناراحتی همه‌مون از اینکه یه سرویس خوب و صادق نمی‌تونه از پس هزینه‌ها بر بیاد، سرویسم رو منتقل کردم به پاس لیارا (https://liara.ir @liara_cloud) . تجربه راحت و خوب. تفاوت‌هایی داشت که کمی کار می‌خواست ولی تا الان کاملا راضی.

jadi
liara testimonial
jadi

یه خسته نباشید باید به تصمیمliara_cloud@بگم،
بعد از چندین روز سرکله زدن با سرویس های مشابه بالاخره تصمیم گرفتم لیارا رو امتحان کنم و باور نمیشه ۱۰ دقیقه بیشتر وقت نبرد،
دمتون گرم.

Arch
liara testimonial
EbadiDev

واسه سرویس PaaS با اختلاف لیارا بهترین رابط کاربری داره و یکی از مزیت‌های سرویس دیتابیس‌شون اینه که خودشون به صورت دوره‌ای بکآپ میگیرن.
...

Ali Najafi
liara testimonial
me_ali_najafi

یکی از کارهای خوبی که جدیداً میکنم اینه که یه دیتابیس روی لیارا میسازم و به پروژه وصل میکنم اینطوری هم خونه و هم محل کار دیتابیس بروز رو دارم و راحت میتونم ادامه بدم کار روliara_cloud@

Navid
liara testimonial
1navid

عاشقliara_cloud@شدم درسته در حد AWS نیست ولی خب تجربه خوبی واسه پروژه های داخل ایران ارائه میده، میتونم رو CD هم اجراش کنم

Amir H Shekari
liara testimonial
vanenshi

همراه شما هستیم

در خصوص سفارش یا استفاده از سرویس‌ها سوالی دارید؟
تلفن واحد فروش:
۰۲۵-۳۳۵۵۷۶۱۹ (روزهای کاری ۹ الی ۱۷)
تلفن واحد فروش: ۳۳۵۵۷۶۱۹-۰۲۵ (روزهای کاری ۹ الی ۱۷)