Node.js 系列學習日誌#19 - 運用 express, socket.io, boostrap 建立一個簡單的聊天室功能

寫過網頁的大家們,大致上都了解 http 是一種無狀態的形式,假設頁面登進來的使用者,為了保存它的登入資訊,通常會使用 session 的觀念作為存放的物件,否則 server 端就不知道 client 端登進來的人是誰,不過今天不講session 的部分,而是我們要透過 socket.io 做雙向溝通,只要一旦建立了連線之後,就可以立即傳訊息出去,Client 的部分也立即接收到,相互取得訊息。

今天要來透過介紹的是簡單的聊天室功能,讓大家了解 node.js 在 socket.io 運作的方式,以下是本章節會學習到的部分:

  • 使用 Bootstrap 規劃製作前端頁面框架
  • 使用 express 套功能
  • 使用 socket.io 套件
  • 開發聊天室功能

本章節程式同步放置於 :https://github.com/weijutu/nodejs-simple-chatroom

首先我們先定義好需要的動向有:

  1. 進入聊天室之前需要先輸入姓名
  2. 進入聊天室頁面之後,左邊區域負責聊天的顯示部分,右邊區域負責登入的名單
  3. 聊天功能會有個按鈕,當使用者輸入訊息後,按下按鈕送出
  4. 瀏覽器關閉時,也等於退出聊天功能

首先建立一個 nodejs-simple-chatroom 資料夾,安裝 express, socket.io 相依套件,package.json 檔案內容為:

1
2
3
4
5
6
7
8
9
{
"name": "nodejs-simple-chatroom",
"version": "0.0.1",
"private": true,
"dependencies": {
"socket.io": "1.0.3",
"express": "4.4.0"
}
}

(安裝相依套件方式請詳情:http://ithelp.ithome.com.tw/ironman7/app/article/dev/recent/10158140)
建立 package.json 完成之後,先將畫面基礎規劃完成

client 端

當多開幾個瀏覽器,可以輸入進入聊天的姓名,以下是在聊天之前先輸入的欄位:

在這邊使用了 bootstrap 前端框架來美化一下聊天室外觀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<div class="container">
<div id="nickWrap">
<form id="setNick" role="form">
<h2 class="form-signin-heading">請輸入您的名字:</h2>
<p id="nickError"></p>
<div style="margin:20px;">
<input size="35" id="txtNickname" required="" />
</div>
<input type="Submit" class="btn btn-lg btn-primary">
</form>
</div>


<div id="contentWrap">
<div id="chatWrap" class="panel panel-default" >
<div class="panel-heading">即時聊天</div>
<div class="panel-body">
<div class="row">
<div class="col-md-9">
<div id="chat"></div>
<form id="send-message">
<div class="input-group">
<input type="text" class="form-control" placeholder="請輸入聊天訊息..." id="message">
<span class="input-group-btn">
<button type="submit" class="btn btn-default">送出留言</button>
</span>
</div>
</form>
</div>
<div class="col-md-3">
線上名單:
<div id="users"></div>
</div>
</div>
</div>
</div>

</div>

<script type="text/javascript" src="//code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>

</div>

javascript 部分

這部分寫在 client 裏面,作為控制像的事件觸發操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<script type="text/javascript">
$(function(){
var socket = io.connect();
var $frmMessage = $('#send-message');
var $frmNick = $('#setNick');
var $nickError = $('#nickError');
var $nickBox = $('#txtNickname');
var $boxMessage = $('#message');
var $chat = $('#chat');


$frmNick.submit(function(e){
console.log($nickBox.val());
console.log('hi, frmNick');
e.preventDefault();

socket.emit('new user', $nickBox.val() );

$nickBox.val('');

$('#nickWrap').hide();
$('#contentWrap').show();

});

$frmMessage.submit(function(e){
e.preventDefault();
socket.emit('send message', $boxMessage.val().trim());
$boxMessage.val('');
});

socket.on('usernames', function(data){
var sb = '';
for(var d = 0; d < data.length; d++ ) {
console.log(data[d]);
sb += data[d] + "<br />";
}
$('div#users').html(sb);

});

socket.on('chat', function(server,msg){

var now = new Date();
var datetime = now.getFullYear()+'/'+(now.getMonth()+1)+'/'+now.getDate();
datetime += ' '+now.getHours()+':'+now.getMinutes()+':'+now.getSeconds();

$chat.append("<br /><i>系統訊息: <b>[ " + msg + " ]</b> (" +
datetime + ")</i><br />");
});

socket.on('new message', function(data){
var msg = data.msg;
var name = data.nick;

var now = new Date();
var datetime = now.getFullYear()+'/'+(now.getMonth()+1)+'/'+now.getDate();
datetime += ' '+now.getHours()+':'+now.getMinutes()+':'+now.getSeconds();

$chat.append("<b>" + name + " </b>: " + msg + " (<i>" + datetime + "<i>)<br />");
});
});
</script>

server 端:(app.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var express = require('express'),
app = express(),
server = require('http').createServer(app),
io = require('socket.io').listen(server),
nicknames = [];

server.listen(3000);

app.get('/', function(req, res){
res.sendfile(__dirname + '/index.html');
});
app.use('/public', express.static(__dirname + '/public'));

io.sockets.on('connection', function(socket) {
socket.on('new user', function(data){
console.log(data);
if (nicknames.indexOf(data) != -1) {

} else {
socket.emit('chat', 'SERVER', '歡迎光臨 ' + data);

socket.nickname = data;
nicknames.push(socket.nickname);
io.sockets.emit('usernames', nicknames);
updateNicknames();
}
});

function updateNicknames(){
io.sockets.emit('usernames', nicknames);
}

//
socket.on('send message', function(data){
io.sockets.emit('new message', { msg: data, nick: socket.nickname });
});

socket.on('disconnect', function(data){
if (!socket.nickname) return;
io.sockets.emit('chat', 'SERVER', socket.nickname + ' 離開了聊天室~');
nicknames.splice(nicknames.indexOf(socket.nickname), 1);
updateNicknames();
});
});

結果

已上圖來講,原本有三個聊天者,當某一個使用者關閉瀏覽器 離開聊天室時,將會把使用者從名單上移除。
本章節是個簡易的聊天室功能,當然也可以進階一點做一些顯示上的呈現,或者將這些的留言都使用資料庫(例如:mongodb)儲存這些訊息。

參考資料

http://getbootstrap.com/
http://expressjs.com
http://socket.io/
https://www.ptt.cc/bbs/joke/M.1412577767.A.0EB.html
http://segmentfault.com/a/1190000000479518