สร้างแอพ Chat ด้วย Node.js และ Socket.IO

Suranart Niamcome
SiamHTML
Published in
4 min readApr 6, 2015
socket.io

วันนี้เรามาลองทำแอพ chat แบบเรียลไทม์ด้วย Node.js กันครับ สมัยก่อนอาจจะดูยุ่งยาก แต่สมัยนี้เราสามารถทำแอพแนวนี้ได้ง่ายเอามากๆ เลย เพื่อไม่ให้เสียเวลา เรามาเริ่มกันเลยฮะ

แต่ก่อนเราทำอย่างไร ?

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

รู้จักกับ Web Socket

วิธีที่น่าสนใจกว่าก็คือการสื่อสารกันผ่านสิ่งที่เรียกว่า Web Socket ครับ เพื่อให้เห็นภาพชัดๆ ผมขอเปรียบเทียบว่ามันก็เหมือนกับการสร้าง “ท่อ” เชื่อมระหว่าง client กับ server เข้าด้วยกันครับ โดยจุดเด่นของ Web Socket ก็คือ มันจะเป็นการสื่อสารแบบ 2 ทางครับ คือใครจะส่งข้อมูลไปหาใครก็ได้ นั่นหมายความว่าเราจะไม่ต้องคอยถาม server แล้วว่ามีใครส่งอะไรมาให้เรามั้ย เพราะเราจะให้ server เป็นคนบอกเราเองครับ นอกจากนั้น เจ้าท่อที่ว่านี้มันจะต่อค้างเอาไว้ด้วยนะครับ คือไม่ได้หายไปเวลาส่งข้อมูลเสร็จ นั่นหมายความว่าการส่งแต่ละครั้งจะไม่มีปัญหาเรื่อง overhead แล้วล่ะครับ

ภาพรวมของแอพ

ทีนี้เรามาดูตัวแอพของเราบ้างครับ ผมมองว่ามันแบ่งออกเป็น 2 ส่วน ดังนี้

  • Server เอาไว้รับข้อความจาก client และกระจายข้อความนั้นไปยัง client ทุกๆ เครื่องที่เชื่อมต่ออยู่
  • Client เอาไว้ส่งข้อความที่จะคุยไปยัง server และรับข้อความของคนอื่นที่ server ส่งมา

เพื่อให้เห็นภาพมากขึ้น สมมติว่าในห้อง chat มีคนอยู่ 3 คน คือ A, B และ C แอพของเราจะต้องทำแบบนี้ได้ครับ

  1. A ส่งข้อความไปยัง server
  2. server ได้รับข้อความจาก A
  3. server ส่งข้อความของ A ไปยัง client ทั้งหมด ซึ่งก็คือ A, B และ C
  4. A, B และ C ได้รับข้อความของ A จาก server

จะสังเกตนะครับว่า A ไม่ได้คุยกับ B และ C ตรงๆ และนี่เป็นเหตุผลครับว่าทำไมเราต้องมี server

รู้จักกับ Socket.IO

Socket.IO เป็นโมดูลของ Node.js ที่เอาไว้เรียกใช้งาน Web Socket ครับ ส่วนวิธีใช้งานนั้นก็ไม่ยากเลย เพราะที่ใช้หลักๆ แล้วจะมีด้วยกัน 2 แบบ เท่านั้นเองครับ

  • socket.on(ชื่อท่อ, callback) เมื่อได้รับข้อมูลมาทางท่อนี้ให้ทำอะไร
  • socket.emit(ชื่อท่อ, ข้อมูลที่จะส่ง) ส่งข้อมูลไปยังทุกๆ client ที่เชื่อมต่อกับท่อนี้อยู่

จะเห็นว่าโค้ดนั้นเข้าใจง่ายมากเลยใช่มั้ยครับ งั้นเรามาเริ่มลงมือเขียนแอพกันเลยดีกว่าครับ

Workshop — เขียนแอพ Instant Messaging

สำหรับ workshop นี้ ผมจะขอถือว่าเราคุ้นเคยกับการใช้ Node.js, Express และ Jade มาเป็นอย่างดีแล้วนะครับ เพราะจะได้เน้นไปที่เนื้อหาเกี่ยวกับการใช้งาน Socket.IO ได้เต็มที่ครับ แต่ถ้าใครอยากจะทบทวนความรู้ ก็สามารถอ่านจากบทความด้านล่างนี้ได้เลยครับ

ก่อนจะเริ่ม workshop หากเพื่อนๆ ยังไม่สะดวกที่จะเขียนโค้ดตามก็ไม่เป็นไรนะครับ แนะนำให้อ่านแล้วพยายามทำความเข้าใจโค้ดก็พอฮะ เพราะที่ท้ายบทความจะมี source code ให้ดาวน์โหลดไปลองเล่นอยู่แล้ว

1. ติดตั้ง Dependency

เริ่มต้นด้วย package.json ตามนี้ได้เลยครับ

{
"name": "realtime-chat",
"version": "0.1.0",
"devDependencies": {
"express": "^4.12.3",
"jade": "^1.9.2",
"socket.io": "^1.3.5"
}
}

จากนั้นก็ติดตั้ง dependency ต่างๆ ด้วยคำสั่ง

npm install

2. เตรียมไฟล์ต่างๆ

โครงสร้างของแอพจะเป็นแบบนี้ครับ ให้เราสร้างไฟล์เปล่าๆ มารอไว้ก่อนได้เลย

|-- app.js
|
|-- node_modules/
| - express
| - jade
| - socket.io
|
|-- package.json
|
|-- public/
| - css/
| - main.css
|
`-- views/
- index.jade

3. เขียนโค้ดที่ app.js

เริ่มกันที่ไฟล์ app.js ครับ ให้เราใส่โค้ดด้านล่างนี้ลงไปได้เลย

var express = require('express');
var app = express();
var path = require('path');
var port = 8081;

var server = app.listen(port, function() {
console.log('Listening on port: ' + port);
});
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.static('public'));

app.get('/', function(req, res) {
res.render('index');
});
จะเห็นว่าเป็นโค้ดสำหรับสร้าง http server ธรรมดาๆ ที่ port 8081 ครับ นอกจากนั้นก็จะมีการกำหนดว่าให้ใช้ Jade เป็น template engine นะ กำหนดให้ Express อ่านไฟล์ static ที่โฟลเดอร์ public นะ แล้วก็ทำ route สำหรับหน้าแรกให้ไปอ่าน template ที่ไฟล์ index.jade ครับ
4. เขียนโค้ดที่ index.jadeจากนั้นมาดูที่ไฟล์ index.jade ครับ ให้เราใส่โค้ดด้านล่างนี้ลงไปdoctype html
html
head
title Realtime Chat using Node.js and Socket.IO
meta(name='viewport', content="initial-scale=1")
link(rel='stylesheet', href='css/main.css')
body
div.box.box--container
div.box.box--chat
ul#chat-history
form#chat-form(action="")
input.box(type="text", id="chat-message", autocomplete="off", placeholder="Enter message here...")
ไล่โค้ดดูก็จะพบว่ามี form ที่เราจะเอาไว้พิมพ์ข้อความสำหรับ chat นั่นเองครับ

5. เขียนโค้ดที่ main.css

เพื่อความสวยงามของแอพเรา ให้ใส่โค้ด css ด้านล่างนี้ ลงไปที่ไฟล์ public/css/main.css ครับ* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 62.5%;
}
body {
background: #ccc;
font-family: arial, san-serif;
font-size: 1.2em;
color: #666;
}
.box {
width: 100%;
border-radius: 3px;
}
.box--container {
max-width: 640px;
margin: 0 auto;
padding: 15px;
}
.box--chat {
background: #fff;
padding: 15px;
border: 1px solid #eee;
}
#chat-history {
list-style: none;
padding: 0 0 10px 0;
overflow: auto;
height: 250px;
}
.message {
background: #eee;
padding: 10px 15px;
border-radius: 20px;
margin-bottom: 5px;
float: left;
clear: both;
}
.message--me {
background: #B3ED7A;
float: right;
}

#chat-message {
background: #f5f5f5;
padding: 10px;
border: 1px solid #d5d5d5;
}
6. ลองรันให้เราลองรันดูเลยครับว่าหน้าตาของแอพเราในตอนนี้เป็นอย่างไรnode app.jsเมื่อลองรันดูก็จะเห็นแอพหน้าตาสวยงาม มีช่องสำหรับพิมพ์ข้อความ แต่พอลองพิมพ์แล้ว Enter ดูก็จะพบว่ายังไม่มีอะไรเกิดขึ้นครับ

7. เรียกใช้ Socket.IO ที่ฝั่ง Server

ที่ไฟล์ app.js ให้เพิ่มโค้ดสำหรับใช้งาน Socket.IO เข้าไปต่อท้ายแบบนี้ครับ// app.js.
.
.
var io = require('socket.io').listen(server);// เมื่อมี client เข้ามาเชื่อมต่อให้ทำอะไร?
io.on('connection', function(socket) {
// แสดงข้อความ "a user connected" ออกมาทาง console
console.log('a user connected');
});
เสร็จแล้วอย่าเพิ่งรันแอพใหม่นะครับ เพราะเราจะต้องไปเซท Socket.IO ที่ฝั่ง client ด้วย
8. เรียกใช้ Socket.IO ที่ฝั่ง Clientให้ไปดูที่ไฟล์ index.jade แล้วไปเพิ่ม script ของ socket.io แบบนี้ครับ// index.jadedoctype html
html
head
.
.
.
body
.
.
.
script(src="https://cdn.socket.io/socket.io-1.3.3.js")
script.
// เชื่อมต่อกับ server ผ่าน Web Socket
var socket = io();
9. ลองรันใหม่เรามาลองรันแอพใหม่ดูอีกทีครับ คราวนี้เมื่อลองเข้าหน้าแอพผ่าน browser แล้วกลับไปดูที่ console เราก็จะเห็นข้อความ a user connected แล้วล่ะครับ หากลองรีเฟรช browser หลายๆ ที เราก็จะเห็นข้อความแสดงตามจำนวนครั้งที่เรารีเฟรชครับ

10. ส่งข้อมูลไปยัง Server

ก่อนหน้านี้เราก็แค่ดัก event ว่าพอมี client เข้ามาเขื่อมต่อ ก็ให้แสดงข้อความออกมาใช่มั้ยครับ ทีนี้เราจะมาลองเขียนโค้ดให้ client เป็นคนส่งข้อมูลไปหา server เองดูบ้าง ให้เข้าไปที่ไฟล์ index.jade อีกทีครับ แล้วเพิ่มโค้ด socket.emit(ชื่อท่อ, ข้อมูลที่จะส่ง) เข้าไปแบบนี้ครับ// index.jadescript.
var socket = io();
// ส่งข้อความ "Hello" ไปหา server ผ่านทางท่อชื่อ "chat"
socket.emit('chat', 'Hello');
จากนั้น ที่ไฟล์ app.js เราก็จะเพิ่มโค้ด socket.on(ชื่อท่อ, callback) เพื่อเอาไว้รอรับข้อมูลที่ client จะส่งมาครับ
// app.jsio.on('connection', function(socket) {
// เมื่อได้รับข้อมูลจากท่อ "chat" ให้ทำอะไร?
socket.on('chat', function(message) {
// แสดงข้อมูลที่ได้ ออกมาทาง console
console.log(message);
});
});
เมื่อลองรันใหม่ เราก็จะเห็นข้อความ Hello ที่ client เป็นคนส่งมา แสดงผลออกมาทาง console ครับ
ชื่อของ "ท่อ" จะเป็นอะไรก็ได้ เพียงแต่ต้องตั้งชื่อให้เหมือนกันทั้งฝั่ง client และ server

11. เปลี่ยนมาส่งข้อมูลด้วย Form Input

ก่อนหน้านี้เราจะระบุข้อความที่จะส่งลงไปตรงๆ เลยใช่มั้ย ทีนี้เราจะเปลี่ยนมาใช้ form input ในการรับข้อความที่จะส่งจาก user กันดีกว่าครับ ให้เราเข้าไปแก้โค้ดที่ไฟล์ index.jade// index.jade// โหลด jQuery มาใช้งาน
script(src="https://code.jquery.com/jquery-2.1.3.min.js")
script.
var socket = io();
// เมื่อ form ถูก submit ให้ทำอะไร?
$('#chat-form').submit(function() {
// ส่งข้อความที่พิมพ์มาไปยัง server ผ่านทางท่อชื่อ "chat"
socket.emit('chat', $('#chat-message').val());
return false;
});
จากนั้นก็ลองรันดูอีกทีครับ ให้เราลองพิมพ์ข้อความอะไรก็ได้ แล้วลอง Enter ดู จากนั้นให้ลองไปดูที่ console เราก็จะเห็นข้อความที่เราพิมพ์ แสดงผลออกมาครับ
12. กระจายข้อมูลไปยังทุกๆ Clientทีนี้มาถึงตา server กันบ้างครับ เราจะเขียนโค้ดให้ server กระจายข้อความเมื่อกี้ไปยัง client ทั้งหมดที่มีการเชื่อมต่อกับท่อที่ชื่อ "chat" อยู่ ให้เราเข้าไปที่ไฟล์ app.js แล้วเปลี่ยนจาก console.log() มาเป็น io.emit() แทน แบบนี้เลยครับ// app.jsio.on('connection', function(socket) {
socket.on('chat', function(message) {
// ส่งข้อความที่ได้ไปหาทุกๆ client ที่เชื่อมต่อกับท่อชื่อ "chat"
io.emit('chat', message);
});
});
แต่แค่นั้นยังไม่พอนะครับ เราจะต้องไปเพิ่มโค้ดรับข้อมูลที่ทางฝั่ง client ด้วย ให้เราเข้าไปที่ไฟล์ index.jade แล้วเพิ่มโค้ดด้านล่างนี้ลงไปครับ
// index.jade// เมื่อได้รับข้อมูลจากท่อ "chat" ให้ทำอะไร?
socket.on('chat', function(message) {
// แสดงผลข้อความที่ได้มาออกมาทางหน้าจอ
$('#chat-history').append($('<li class="message">').text(message));
});
เรียบร้อยแล้วครับ เรามาลองเล่นดูเลย จะเห็นว่าเวลาเราพิมพ์อะไรลงไป ข้อความที่เราพิมพ์ก็จะขึ้นมาด้วยครับ แนะนำให้ลองเปิดมาอีก browser นึง แล้วลอง chat กับตัวเองดู เราก็จะพบว่าข้อความที่คุยกันมันอัพเดทแบบเรียลไทม์เลยล่ะครับ
บทสรุปจะเห็นว่าการใช้ Node.js ร่วมกับ Socket.IO นั้น ช่วยให้เราสามารถสร้างแอพ Instant Messaging ขึ้นมาได้ง่ายๆ เลยใช่มั้ยครับ จริงๆ แล้ว การใช้ Socket.IO หลักๆ ก็มีอยู่เท่านี้ฮะ เราก็แค่ไปตั้งชื่อท่อของทั้งสองฝั่ง หากท่อชื่อตรงกันก็จะคุยกันรู้เรื่องครับจะว่าไปแล้ว คำว่า "ท่อ" ที่ผมใช้บ่อยๆ ในบทความนี้นั้น มันก็เหมือนกับ "ช่อง" ในการออกอากาศของทีวีครับ เพราะสัญญาณทีวีนั้นกระจายอยู่ทั่วไปในอากาศ การที่เรารับภาพจากช่องนี้ได้ก็เป็นเพราะช่องที่เราเปิดรับสัญญาณไว้นั้นตรงกับสัญญาณที่ออกอากาศมานั่นเองครับ ในทำนองเดียวกัน แม้ว่าสัญญาณช่องหนึ่งจะถูกออกอากาศมา แต่ถ้าเราไม่ได้เปิดรับสัญญาณของช่องนั้นเอาไว้ เราก็จะไม่ได้รับภาพของช่องนั้นครับผมอยากให้เพื่อนๆ ไปลองต่อยอดแอพนี้ให้มันใช้งานได้จริงๆ นะครับ หรือจะลองใช้ Socket.IO ทำแอพแนวอื่นดูก็ได้ฮะ แอพไหนซับซ้อนหน่อยก็อาจจะใช้หลายๆ ท่อเข้ามาช่วย แล้วแต่เราจะออกแบบเลยครับ ส่วนตัวผมเองก็ได้ลองต่อยอดแอพนี้ไปนิดหน่อยครับ คือจะแยกสีข้อความของตัวเองออกจากข้อความของคนอื่นด้วย เพื่อนๆ คนไหนทำแอพเสร็จแล้ว อย่าลืมเอามาให้ลองเล่นกันบ้างนะครับ
Instant Messaging Screenshot
Download source

--

--