Single‧9 |
Posted: 18 Dec 2017 06:12 PM PST 許久沒有寫文章了(你好像常這樣說欸!),今天要來稍微講講專門用來實作即時通訊的 Node.js 模組 – Socket.io。 Socket.io 其實是一個完整實作 Websocket 的函式庫,他提供更簡單的方式讓開發者可以方便的使用 Websocket 這樣的通訊技術來實作許多應用。 我將以實作一個簡易聊天室作為範例來介紹這一個模組。
開始之前這篇文章的程式範例將以 Node.js + Socket.io 為主,因此你必須先安裝好 Node.js 的環境,安裝方式請直接至官方網站下載安裝即可。 確認裝好 Node.js 的環境後,我們要來做一些基礎設定以及安裝該有的基本模組啦!請在你想要開發的資料夾中開啟終端機,輸入以下指令: npm init -y npm install -S express socket.io 這兩行指令,第一行是在資料夾中產生一個 第二行指令則是安裝我們所需的兩種模組:Express.js、socket.io。 接下來,我們在專案資料夾中開一個新檔案: 一切就緒後,我們就可以開始進行了。 伺服器程式HTTP凡與 HTTP 有關係的東西就離不開 HTTP 伺服器啦!在 Node.js 中,建一個 HTTP 的伺服器非常簡單,大概就這不到 10 行的程式碼就能完成了吧。那個,別發呆,還不趕快把下面這幾行程式碼輸入到 const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('Hello, World!'); }); app.listen(3000, () => { console.log("Server Started. http://localhost:3000"); }); 然後輸入這個指令來啟動伺服器: node index.js 你應該會看到終端機顯示這樣的字樣: Server Started. http://localhost:3000 好了,你可以打開瀏覽器,然後輸入這個網址:http://localhost:3000 你的瀏覽器應該會出現 Hello, World! HTTP 伺服器完成! Socket.io再來是Socket.io的部分,這邊我們要稍微修改一下 const express = require('express'); const app = express(); // 加入這兩行 const server = require('http').Server(app); const io = require('socket.io')(server); app.get('/', (req, res) => { res.send('Hello, World!'); }); // 當發生連線事件 io.on('connection', (socket) => { console.log('Hello!'); // 顯示 Hello! // 當發生離線事件 socket.on('disconnect', () => { console.log('Bye~'); // 顯示 bye~ }); }); // 注意,這邊的 server 原本是 app server.listen(3000, () => { console.log("Server Started. http://localhost:3000"); }); 啟動方式跟我們在執行HTTP伺服器時一樣 node index.js 然後你會發現什麼都沒有變。 是的,因為我們的網站頁面還沒有放上去,當然什麼都不會變啊!畢竟這時候瀏覽器也不知道要打開Websocket呢!不信?那你連看看http://localhost:3000,看伺服器有沒有出現 所以呢,接下來我們要透過網頁來與伺服器搭上線,抓緊,我們要準備起飛了。 網頁伺服器大致完成後,要來處理人看得到的部分,也就是網頁呈現的部分。總不能只顯示個 Hello, World!,然後什麼事都不能做吧!?這樣還要聊個毛天啊! 所以,接下來請在你的專案資料夾下建立一個資料夾,這個資料夾專門用來存放網頁程式用,我想我們就叫做… 然後在 這個檔案將會呈現我們聊天室的主要畫面,請記得在檔案裝輸入下方的程式碼。 <!DOCTYPE html> <html lang="zh-tw"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Chatroom</title> <script src="/socket.io/socket.io.js"></script> <script> var socket = io(); </script> </head> <body> <div>什麼都沒有做,只有連線。</div> </body> </html> 然後修改 // 修改這一部分 app.get('/', (req, res) => { res.sendFile( __dirname + '/views/index.html'); }); 這樣伺服器才會把我們剛剛的網頁內容推到瀏覽器上顯示。 那麼,這其實只是一個非常基本的頁面,只有一個目標,就是讓客戶端與伺服器的 WebSocket 連接埠建立連線。要確認有無連線,請重新整理剛剛打開的瀏覽器畫面,然後按下重新整理,你應該會在終端機中看到 如果沒的話,重新執行一次伺服器吧,請記得先用 收發測試現在我們要來做一點實驗,我們先在 <body> <div id="msg"></div> <script> // 當觸發連線建立事件 // 發送 greet 事件給伺服器 socket.on("connect", function () { socket.emit("greet"); }); // 當收到伺服器回傳到 greet 事件 // 將內容轉到 div 中呈現 socket.on("greet", function (msg) { document.getElementById("msg").innerText = msg; }); </script> </body> 喔對,還有修改伺服器的程式 io.on('connection', (socket) => { console.log('Hello!'); // 加入這一段 // 接收來自前端的 greet 事件 // 然後回送 greet 事件,並附帶內容 socket.on("greet", () => { socket.emit("greet", "Hi! Client."); }); //... }); 重新啟動伺服器然後重新整理網頁,你應該會看到網頁上顯示 在這一部份中,我們重新調整了伺服器的程式,以及前端網頁的JavaScript程式。 在前端網頁的部分
在伺服器的部分
介面設計秉持著自己的玩具自己做的精神,本教學不使用過多額外的框架,避免過於複雜的學習難度,所以在這邊你會看到許多原生的語法 喔對,如果要說框架的話,我有用到一個:Vanilla.js 回到正題。 一個正常的聊天室,我們需要幾個基本功能:
所以我們的介面大概會長這樣…等等你就會看到了。 好,開始囉。 index.html 在 </head> 之前加入 <style> html, body { padding: 0; margin: 0; } #container { top: 50px; width: 500px; margin: 0 auto; display: block; position: relative; } #status-box { text-align: right; font-size: .6em; } #content { width: 100%; height: 350px; border: 1px solid darkolivegreen; border-radius: 5px; overflow: auto; } #send-box { width: 100%; text-align: center; } #send-box input { display: inline-block; } input[name="name"] { width: 15%; } input[name="msg"] { width: 70%; } input[type="button"] { width: 10%; } .msg { width: 73%; display: inline-block; padding: 5px 0 5px 10px; } .msg > span { width: 25%; display: inline-block; } .msg > span::before { color: darkred; content: " { "; } .msg > span::after { color: darkred; content: " } "; } </style> 將 <body> <div id="container"> <div id="status-box">Server: <span id="status">-</span> / <span id="online">0</span> online.</div> <div id="content"> <div class="msg"> <span class="name">Duye</span> Hello! </div> <div class="msg"> <span class="name">Alice</span> Hi! </div> </div> <div id="send-box"> <form id="send-form"> <input type="text" name="name" id="name" placeholder="暱稱"> <input type="text" name="msg" id="msg" placeholder="說點什麼?"> <input type="submit" value="送出"> </form> </div> </div> <script> document.addEventListener("DOMContentLoaded", () => { var status = document.getElementById("status"); var online = document.getElementById("online"); socket.on("connect", function () { status.innerText = "Connected."; }); socket.on("disconnect", function () { status.innerText = "Disconnected."; }); socket.on("online", function (amount) { online.innerText = amount; }); }); </script> </body> 重新整理網頁後應會看到一個新的畫面,這時還沒有任何功能,雖然有點簡陋,不過該有的都有啦,別嫌 XD。 接下來我們要來修改伺服器的部分,順便讓伺服器狀態這個功能動起來。 index.js const server = require('http').Server(app); const io = require('socket.io')(server); // 加入線上人數計數 let onlineCount = 0; // 修改 connection 事件 io.on('connection', (socket) => { // 有連線發生時增加人數 onlineCount++; // 發送人數給網頁 io.emit("online", onlineCount); socket.on("greet", () => { socket.emit("greet", onlineCount); }); socket.on('disconnect', () => { // 有人離線了,扣人 onlineCount = (onlineCount < 0) ? 0 : onlineCount-=1; io.emit("online", onlineCount); }); }); 重啟伺服器與網頁,注意右上角的資訊,理應會看到: Server: Connected. / 1 online. 這樣的資訊才對,如果沒有,那應該是失敗了,請再做一次看看。 這時你可以再開一個網頁視窗,應該會看到右上角的數字變成 2。 訊息輸入搞定了簡單的外觀後,接下來是訊息輸入的部分,也就是聊天內容輸入。 index.html document.addEventListener("DOMContentLoaded", () => { var status = document.getElementById("status"); var online = document.getElementById("online"); var sendForm = document.getElementById("send-form"); // 加入這行 //... // 加入這段 sendForm.addEventListener("submit", function (e) { e.preventDefault(); var formData = {}; var formChild = sendForm.children; for (var i=0; i< sendForm.childElementCount; i++) { var child = formChild[i]; if (child.name !== "") { formData[child.name] = child.value; } } socket.emit("send", formData); }); }); index.js io.on('connection', (socket) => { //... // 加入這段 socket.on("send", (msg) => { console.log(msg) }); //... }); 現在重新啟動伺服器和網頁,然後嘗試輸入一些東西後送出,觀察終端機的變化吧~ 這裡我們在前端網頁的部分加入
伺服器
顯示聊天訊息完成了送出的部分,並且也確認伺服器能正確收到資料後,我們要來讓聊天室可以顯示這些聊天資料啦! index.html <!-- 刪除這部分 --> <div class="msg"> <span class="name">Duye</span> Hello! </div> <div class="msg"> <span class="name">Alice</span> Hi! </div> document.addEventListener("DOMContentLoaded", () => { //... var sendForm = document.getElementById("send-form"); var content = document.getElementById("content"); // 加入這行 //... socket.on("online", function (amount) { online.innerText = amount; }); // 加入這一段 socket.on("msg", function (d) { var msgBox = document.createElement("div") msgBox.className = "msg"; var nameBox = document.createElement("span"); nameBox.className = "name"; var name = document.createTextNode(d.name); var msg = document.createTextNode(d.msg); nameBox.appendChild(name); msgBox.appendChild(nameBox); msgBox.appendChild(msg); content.appendChild(msgBox); }); //... }); index.js // 修改 console.log 成 io.emit socket.on("send", (msg) => { // 廣播訊息到聊天室 io.emit("msg", msg); }); 好,現在重啟伺服器,開兩個視窗,連上伺服器,你可以開始聊天了! 空白輸入我想你有發現到,當你沒有輸入任何東西直接送出時還是能送出,這其實是一件很詭異的事情,所以我們要來解決這個問題。 index.html
#send-box input.error { border: 1px solid red; }
這樣前端網頁發現值為空時,除了警告提示外,也不會將事件送出去給伺服器。但這樣還不夠,如果有人直接對 WebSocket 送事件的話還是會被發現,所以我們要讓後端也能做一次驗證. index.js //... // 修改 send 事件監聽器 socket.on("send", (msg) => { // 如果 msg 內容鍵值小於 2 等於是訊息傳送不完全 // 因此我們直接 return ,終止函式執行。 if (Object.keys(msg).length < 2) return; // 廣播訊息到聊天室 io.emit("msg", msg); }); 伺服器端的驗證很簡單,只判斷鍵值長度是否小於2,而2這個長度則是因為我們的訊息內寫包含有兩個東西
好了,你可以嘗試把 結束之後Demo如果你有點懶得動手做,這邊有實品展示可以玩玩。 完整程式感謝你看完這篇文章,如果你也有跟著時實做完文章內容,你應該會看到這樣的完整程式碼: index.html <!DOCTYPE html> <html lang="zh-tw"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Chatroom</title> <script src="/socket.io/socket.io.js"></script> <script> var socket = io(); </script> <style> html, body { padding: 0; margin: 0; } #container { top: 50px; width: 500px; margin: 0 auto; display: block; position: relative; } #status-box { text-align: right; font-size: .6em; } #content { width: 100%; height: 350px; border: 1px solid darkolivegreen; border-radius: 5px; overflow: auto; } #send-box { width: 100%; text-align: center; } #send-box input { display: inline-block; } #send-box input.error { border: 1px solid red; } input[name="name"] { width: 15%; } input[name="msg"] { width: 70%; } input[type="button"] { width: 10%; } .msg { width: 73%; display: inline-block; padding: 5px 0 5px 10px; } .msg > span { width: 25%; display: inline-block; } .msg > span::before { color: darkred; content: " { "; } .msg > span::after { color: darkred; content: " } "; } </style> </head> <body> <div id="container"> <div id="status-box">Server: <span id="status">-</span> / <span id="online">0</span> online.</div> <div id="content"> </div> <div id="send-box"> <form id="send-form"> <input type="text" name="name" id="name" placeholder="暱稱"> <input type="text" name="msg" id="msg" placeholder="說點什麼?"> <input type="submit" value="送出"> </form> </div> </div> <script> document.addEventListener("DOMContentLoaded", () => { var status = document.getElementById("status"); var online = document.getElementById("online"); var sendForm = document.getElementById("send-form"); var content = document.getElementById("content"); socket.on("connect", function () { status.innerText = "Connected."; }); socket.on("disconnect", function () { status.innerText = "Disconnected."; }); socket.on("online", function (amount) { online.innerText = amount; }); socket.on("msg", function (d) { var msgBox = document.createElement("div") msgBox.className = "msg"; var nameBox = document.createElement("span"); nameBox.className = "name"; var name = document.createTextNode(d.name); var msg = document.createTextNode(d.msg); nameBox.appendChild(name); msgBox.appendChild(nameBox); msgBox.appendChild(msg); content.appendChild(msgBox); }); sendForm.addEventListener("submit", function (e) { e.preventDefault(); var ok = true; var formData = {}; var formChild = sendForm.children; for (var i=0; i< sendForm.childElementCount; i++) { var child = formChild[i]; if (child.name !== "") { var val = child.value; if (val === "" || !val) { ok = false; child.classList.add("error"); } else { child.classList.remove("error"); formData[child.name] = val; } } } if (ok) socket.emit("send", formData); }); }); </script> </body> </html> index.js const express = require('express'); const app = express(); const server = require('http').Server(app); const io = require('socket.io')(server); // 加入線上人數計數 let onlineCount = 0; app.get('/', (req, res) => { res.sendFile( __dirname + '/views/index.html'); }); io.on('connection', (socket) => { // 有連線發生時增加人數 onlineCount++; // 發送人數給網頁 io.emit("online", onlineCount); socket.on("greet", () => { socket.emit("greet", onlineCount); }); socket.on("send", (msg) => { // 如果 msg 內容鍵值小於 2 等於是訊息傳送不完全 // 因此我們直接 return ,終止函式執行。 if (Object.keys(msg).length < 2) return; // 廣播訊息到聊天室 io.emit("msg", msg); }); socket.on('disconnect', () => { // 有人離線了,扣人 onlineCount = (onlineCount < 0) ? 0 : onlineCount-=1; io.emit("online", onlineCount); }); }); server.listen(3000, () => { console.log("Server Started. http://localhost:3000"); }); 最後這篇文章重點:
感謝收看,我們下次再見~ The post Node.js 與 Socket.io – 即時聊天室實作 appeared first on Single.9. |
You are subscribed to email updates from Single.9. To stop receiving these emails, you may unsubscribe now. |
Email delivery powered by Google |
Google, 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States |