🔵 Node.js

เจาะลึก Real-time Communication ด้วย Socket.IO และความต่างระหว่าง WebSockets vs HTTP

10 นาที 12 views บันทึกเป็น PDF
เจาะลึก Real-time Communication ด้วย Socket.IO และความต่างระหว่าง WebSockets vs HTTP

อยากทำระบบแชตหรือแจ้งเตือนแบบเรียลไทม์ไหม? มาเรียนรู้พื้นฐาน WebSockets และวิธีใช้ Socket.IO เพื่อส่งข้อมูลทันทีโดยไม่ต้องรีเฟรชหน้าเว็บกัน

ทำความรู้จักกับ Real-time Communication และเหตุผลที่ต้องใช้

เวลาเราเล่นเว็บปกติ เรามักจะเห็นข้อมูลอัปเดตก็ต่อเมื่อเรากดรีเฟรชหน้าเว็บใหม่เสมอ ซึ่งกระบวนการนี้เรียกว่า HTTP (โปรโตคอลมาตรฐานที่ใช้รับส่งข้อมูลบนเว็บ) ทำงานแบบขอแล้วตอบ คือฝั่งเราถามไป ฝั่งเซิร์ฟเวอร์ถึงจะตอบกลับมา ถ้าเราไม่ถาม เซิร์ฟเวอร์ก็จะไม่ส่งอะไรมาให้เราเลย ซึ่งมันไม่เหมาะกับแอปที่ต้องการความสดใหม่ตลอดเวลา

ลองจินตนาการถึงระบบแชต ถ้าเราต้องกดรีเฟรชหน้าเว็บทุกๆ 5 วินาทีเพื่อดูว่าเพื่อนตอบกลับมาหรือยัง มันคงเป็นประสบการณ์ที่แย่มาก Real-time Communication (การสื่อสารที่ข้อมูลอัปเดตทันทีโดยไม่ต้องรอ) จึงเข้ามาแก้ปัญหานี้ เพื่อให้แอปพลิเคชันของเราสามารถส่งข้อมูลหากันได้แบบสองทางตลอดเวลาเหมือนการคุยโทรศัพท์

สำหรับมือใหม่ที่กำลังฝึก Node.js (สภาพแวดล้อมที่ช่วยให้รันภาษา JavaScript บนเซิร์ฟเวอร์ได้) การเข้าใจเรื่องการสื่อสารแบบเรียลไทม์จะช่วยเปิดประตูให้เราทำโปรเจกต์เจ๋งๆ ได้มากมาย ไม่ว่าจะเป็นระบบแจ้งเตือน (Notification) ระบบกระดานหุ้น หรือแม้แต่เกมออนไลน์ง่ายๆ บนเบราว์เซอร์ ซึ่งนี่คือทักษะสำคัญที่ตลาดงานต้องการอย่างมาก

WebSockets vs HTTP: ต่างกันอย่างไรในมุมมองโปรแกรมเมอร์

ความแตกต่างสำคัญระหว่าง HTTP และ WebSockets (ช่องทางสื่อสารที่เปิดค้างไว้เพื่อส่งข้อมูลได้ตลอดเวลา) คือเรื่องของสถานะการเชื่อมต่อ ปกติ HTTP จะเปิดการเชื่อมต่อ ส่งข้อมูลเสร็จแล้วปิดทันที ทำให้เซิร์ฟเวอร์ไม่สามารถส่งข้อมูลหาเราได้เองถ้าเราไม่ได้เป็นคนเริ่มถามก่อน

แต่ WebSockets จะใช้วิธีการ "จับมือ" กันครั้งแรกผ่าน HTTP แล้วเปลี่ยนช่องทางนั้นให้กลายเป็นท่อสื่อสารที่เปิดค้างไว้ตลอดเวลา ทำให้เซิร์ฟเวอร์สามารถยิงข้อมูล (Push) มาหาเราได้ทันทีที่เหตุการณ์เกิดขึ้น โดยที่เราไม่ต้องไปนั่งกดถามซ้ำๆ ให้เปลืองทรัพยากรเครื่องอีกต่อไป

สำหรับการพัฒนาเว็บในยุคนี้ เรามักจะใช้ไลบรารีอย่าง Socket.IO (เครื่องมือที่ช่วยให้เราใช้งาน WebSockets ได้ง่ายและเสถียรขึ้น) เพราะมันช่วยจัดการเรื่องความซับซ้อนต่างๆ เช่น การเชื่อมต่อใหม่โดยอัตโนมัติหากเน็ตหลุด หรือการรองรับเบราว์เซอร์เก่าๆ ที่ไม่รู้จัก WebSockets ทำให้ชีวิตโปรแกรมเมอร์อย่างเราง่ายขึ้นมาก

// ตัวอย่างการติดตั้ง Socket.IO ฝั่งเซิร์ฟเวอร์
const express = require('express'); // ดึงไลบรารี Express มาใช้สร้างเว็บ
const http = require('http'); // ดึงไลบรารี HTTP มาสร้างเซิร์ฟเวอร์
const { Server } = require('socket.io'); // ดึงคลาส Server จาก Socket.IO

const app = express();
const server = http.createServer(app);
const io = new Server(server); // สร้าง Socket.IO ขึ้นมาครอบเซิร์ฟเวอร์หลัก

io.on('connection', (socket) => { // เมื่อมีคนเชื่อมต่อเข้ามา
  console.log('มีผู้ใช้งานเชื่อมต่อเข้ามาแล้ว'); // แสดงข้อความใน Terminal
});

server.listen(3000, () => { // เริ่มรันเซิร์ฟเวอร์ที่พอร์ต 3000
  console.log('เซิร์ฟเวอร์รันอยู่ที่ http://localhost:3000');
});

บรรทัดแรกเริ่มจากการเรียกใช้งานโมดูลที่จำเป็น ส่วน io.on('connection') คือจุดสำคัญที่คอยดักฟังว่ามีใครเชื่อมต่อเข้ามาบ้าง ถ้ามีคนเข้ามา เราก็แค่สั่งให้มันแสดงข้อความในคอนโซล ผลลัพธ์ที่ได้คือเมื่อรันไฟล์นี้ใน Terminal คุณจะเห็นข้อความว่า "เซิร์ฟเวอร์รันอยู่ที่ http://localhost:3000" และเมื่อเปิดเบราว์เซอร์เข้าหน้านี้ จะเห็นข้อความ "มีผู้ใช้งานเชื่อมต่อเข้ามาแล้ว" ปรากฏขึ้นมาทันที

การสร้างระบบแชตพื้นฐานด้วย Socket.IO

หัวใจสำคัญของระบบแชตคือการรับส่งข้อความ (Event) ระหว่างฝั่งไคลเอนต์ (ฝั่งผู้ใช้งานหรือเบราว์เซอร์) และฝั่งเซิร์ฟเวอร์ โดยเราจะใช้คำสั่ง emit (การส่งสัญญาณออกไป) เพื่อส่งข้อมูล และใช้ on (การรอรับสัญญาณ) เพื่อคอยฟังข้อมูลที่ถูกส่งเข้ามา

ลองนึกภาพว่าเรามีห้องแชต ถ้ามีคนพิมพ์ข้อความเข้ามา เซิร์ฟเวอร์ต้องทำหน้าที่รับข้อความนั้นแล้ว "กระจาย" ออกไปให้ทุกคนที่อยู่ในห้องเห็นเหมือนกัน ซึ่ง Socket.IO มีฟังก์ชันที่เรียกว่า io.emit ซึ่งทำหน้าที่ส่งข้อมูลไปยังทุกเครื่องที่เชื่อมต่ออยู่พร้อมกันได้เลยแบบรวดเร็ว

มือใหม่มักพลาดเรื่องการลืมจัดการเหตุการณ์ในฝั่งไคลเอนต์ให้ตรงกับฝั่งเซิร์ฟเวอร์ เช่น ตั้งชื่อเหตุการณ์ว่า message แต่ฝั่งหนึ่งสะกดผิดเป็น msg ทำให้ข้อมูลไม่ถึงกัน ดังนั้นการตั้งชื่อเหตุการณ์ให้ชัดเจนและเป็นระบบจึงเป็นนิสัยที่ดีของโปรแกรมเมอร์มืออาชีพ

// ฝั่งเซิร์ฟเวอร์: รับข้อความแล้วกระจายต่อ
io.on('connection', (socket) => {
  socket.on('chat message', (msg) => { // รอรับเหตุการณ์ชื่อ chat message
    io.emit('chat message', msg); // ส่งข้อความที่ได้รับไปยังทุกคน
  });
});

โค้ดชุดนี้ใช้ socket.on เพื่อรอรับข้อมูลที่ส่งมาจากผู้ใช้คนใดคนหนึ่ง เมื่อเซิร์ฟเวอร์ได้รับแล้ว ก็จะใช้ io.emit เพื่อส่งข้อความนั้นกลับไปยังเบราว์เซอร์ของทุกคนที่เชื่อมต่ออยู่ ผลลัพธ์คือถ้าคุณเปิดเบราว์เซอร์สองหน้าต่างพร้อมกัน เมื่อพิมพ์แชตในหน้าต่างแรก ข้อความจะไปปรากฏในหน้าต่างที่สองทันทีโดยไม่ต้องรีเฟรชหน้าเว็บ

การส่งข้อมูลแจ้งเตือน (Notification) แบบเจาะจงบุคคล

บางครั้งเราไม่ได้อยากส่งข้อมูลให้ทุกคน แต่ต้องการส่งให้แค่บางคน เช่น ระบบแจ้งเตือนว่า "มีคนกดไลก์รูปของคุณ" ซึ่งเราต้องระบุให้ชัดเจนว่าส่งให้ใคร โดย Socket.IO จะสร้าง socket.id (รหัสประจำตัวเฉพาะของการเชื่อมต่อแต่ละครั้ง) มาให้เราใช้งานโดยอัตโนมัติ

เราสามารถเก็บรหัสเหล่านี้ไว้ในฐานข้อมูลหรือตัวแปรในหน่วยความจำ เพื่อระบุว่าผู้ใช้งานคนไหนใช้รหัสอะไร แล้วเมื่อถึงเวลาแจ้งเตือน เราก็แค่ส่งข้อมูลไปยังรหัสเฉพาะตัวนั้นๆ โดยใช้คำสั่ง socket.to(id).emit ซึ่งจะส่งไปหาแค่เครื่องที่ระบุไว้เท่านั้น

การเข้าใจเรื่องการระบุตัวตนนี้สำคัญมากสำหรับการทำแอปจริง เพราะถ้าเราส่งข้อมูลผิดพลาดไปหาคนอื่นนอกจากจะน่ารำคาญแล้ว ยังเป็นเรื่องของความเป็นส่วนตัวด้วย ดังนั้นการทดสอบระบบด้วยการเปิดหลายๆ เบราว์เซอร์เพื่อจำลองหลายผู้ใช้งานจึงเป็นสิ่งที่ขาดไม่ได้

// ส่งข้อความหาคนๆ เดียวผ่าน socket.id
io.on('connection', (socket) => {
  console.log('User ID:', socket.id); // พิมพ์ ID ออกมาดูใน Terminal
  
  // สมมติว่าอยากส่งข้อความหาคนนี้คนเดียว
  socket.emit('private message', 'สวัสดี นี่คือข้อความส่วนตัวถึงคุณ');
});

ในโค้ดนี้เราดึงค่า socket.id ออกมาเพื่อตรวจสอบว่าใครเชื่อมต่อเข้ามาบ้าง และใช้ socket.emit ภายในบล็อกการเชื่อมต่อเพื่อส่งข้อความไปหาคนนั้นโดยตรง ผลลัพธ์ที่ได้คือในหน้าเว็บของผู้ใช้คนนั้นจะได้รับข้อความต้อนรับส่วนตัวทันทีที่เชื่อมต่อสำเร็จ ในขณะที่คนอื่นที่เชื่อมต่อเข้ามาในเวลาเดียวกันก็จะได้ ID ที่แตกต่างกันไป

ข้อควรระวังและแนวปฏิบัติที่ดีในการใช้ Real-time

สิ่งที่มือใหม่ต้องระวังคือการส่งข้อมูลมากเกินไปจนเซิร์ฟเวอร์รับไม่ไหว (Overload) การเปิดการเชื่อมต่อค้างไว้หลายร้อยหลายพันคนพร้อมกันกินทรัพยากรเครื่องมากกว่า HTTP ปกติมาก ดังนั้นเราต้องวางแผนให้ดีว่าข้อมูลไหนจำเป็นต้องเป็นเรียลไทม์ และข้อมูลไหนแค่ดึงจากฐานข้อมูลปกติก็พอ

อีกเรื่องที่สำคัญคือความปลอดภัย การสื่อสารผ่าน WebSockets ก็สามารถถูกดักฟังหรือปลอมแปลงข้อมูลได้ ถ้าเราทำระบบแชตที่มีการล็อกอิน ต้องตรวจสอบสิทธิ์ทุกครั้งก่อนอนุญาตให้เชื่อมต่อ หรือตรวจสอบก่อนจะส่งข้อมูลสำคัญผ่านซ็อกเก็ต เพื่อป้องกันการโจมตีจากผู้ไม่หวังดี

สุดท้ายคือเรื่อง Scalability (ความสามารถในการรองรับผู้ใช้งานที่เพิ่มขึ้นเรื่อยๆ) เมื่อแอปของคุณเติบโตขึ้น เซิร์ฟเวอร์ตัวเดียวอาจไม่พอ คุณอาจต้องใช้เทคนิคอย่าง Redis (ฐานข้อมูลที่เก็บข้อมูลในหน่วยความจำเพื่อความเร็ว) เข้ามาช่วยจัดการซิงค์ข้อมูลระหว่างเซิร์ฟเวอร์หลายตัว เพื่อให้ระบบแชตยังทำงานได้ต่อเนื่องแม้คนจะเยอะขึ้น

สรุป: เริ่มต้นสร้างโปรเจกต์เรียลไทม์ด้วยตัวเอง

การเรียนรู้ Socket.IO ไม่ได้เป็นเรื่องยากอย่างที่คิด หากเราเข้าใจพื้นฐานว่ามันคือการสร้างช่องทางสื่อสารที่เปิดค้างไว้เพื่อส่งข้อมูลแบบทันทีทันใด การเริ่มต้นฝึกจากโปรเจกต์เล็กๆ เช่น ระบบแชตกลุ่ม หรือระบบแจ้งเตือนสถานะออนไลน์ของเพื่อน จะช่วยให้คุณเห็นภาพชัดเจนที่สุด

คำแนะนำสำหรับคนที่กำลังฝึกคือ ให้ลองสร้างระบบแชตที่สามารถระบุชื่อผู้ส่งได้ หรือลองทำระบบนับจำนวนคนออนไลน์แบบเรียลไทม์ สิ่งเหล่านี้จะทำให้คุณเข้าใจการใช้งาน emit และ on ได้ลึกซึ้งขึ้น และเป็นพื้นฐานที่ดีในการขยับไปทำแอปพลิเคชันที่ซับซ้อนกว่านี้ในอนาคต

จงจำไว้ว่าการเขียนโปรแกรมที่ดีเริ่มจากการลงมือทำจริง อย่าอ่านแค่ทฤษฎี ให้ลองติดตั้ง socket.io ในโปรเจกต์ Node.js ของคุณแล้วลองเล่นดู หากติดบั๊ก (ข้อผิดพลาดในโค้ด) ก็ให้ใช้ console.log ตรวจสอบค่าในแต่ละขั้นตอน นี่คือวิธีที่โปรแกรมเมอร์ทุกคนใช้เพื่อเก่งขึ้นในทุกๆ วัน

แชร์บทความ

Facebook X LINE

บทความที่เกี่ยวข้อง

คู่มือการทำ Production Deployment: รันโปรเจกต์ด้วย PM2 และ Docker ให้เสถียรเหมือนมือโปร
Node.js

คู่มือการทำ Production Deployment: รันโปรเจกต์ด้วย PM2 และ Docker ให้เสถียรเหมือนมือโปร

เรียนรู้วิธีนำโปรเจกต์ขึ้นเซิร์ฟเวอร์จริงให้รันได้ 24 ชั่วโมงด้วย PM2 และใช้ Docker ช่วยคุมสภาพแวดล้อมให้เหมือนกันทุกเครื่อง ลดปัญหาโค้ดพังเมื่อย้ายที่

1 week ago 11 นาที
14 views
เปลี่ยนโปรเจกต์ Node.js เป็น TypeScript: วิธีตั้งค่าและใช้งานสำหรับมือใหม่
Node.js

เปลี่ยนโปรเจกต์ Node.js เป็น TypeScript: วิธีตั้งค่าและใช้งานสำหรับมือใหม่

อยากเปลี่ยนจาก Node.js มาใช้ TypeScript แต่ไม่รู้เริ่มยังไง? บทความนี้สอนตั้งค่า tsconfig.json, การรันด้วย tsx และการทำ Type Checking ให้ Express แบบเข้าใจง่าย

1 week ago 9 นาที
12 views
สอนเขียน Automated Testing ด้วย Jest และ Supertest สำหรับมือใหม่
Node.js

สอนเขียน Automated Testing ด้วย Jest และ Supertest สำหรับมือใหม่

เลิกทดสอบโค้ดด้วยมือแบบเดิม ๆ มาหัดเขียน Automated Testing ด้วย Jest และ Supertest เพื่อเช็กฟังก์ชันและ API ให้ทำงานถูกต้องแม่นยำ ลดบั๊กกวนใจ

1 week ago 9 นาที
15 views