大致上了解了Socket.IO的使用方式,接下來就來試試看,怎樣用它做出簡單的Chat功能。
基本的Chat應用,在伺服器端其實跟單純的Echo差不多。只是原本伺服器中connection事件取得的socket物件,原本只能傳送訊息給自己,這樣就無法做到像Chat的群播功能。
不過像這樣的基本需求,Socket.IO已經考慮好了,所以只要加上broadcast,把原本的 socket.emit('事件') 改成 socket.broadcast.emit('事件') ,就能把訊息傳送給自己之外的所有人。
除了訊息之外,使用Chat的時候,至少要知道是誰發的,所以額外加上一個簡單的設定nickname的功能,並且在有重複的nickname時自動加上遞增的數字來區別。
伺服器的程式很簡單:
var fs = require('fs');
var app = require('http').createServer(function(req, res) {
fs.readFile('test830.html', function(err, data) {
if(err) {
res.writeHead(500);
return res.end('Error reading default index.');
} else {
res.writeHead(200);
res.end(data);
}
});
});
var nickname = {};
var io = require('socket.io').listen(app);
io.sockets.on('connection', function(socket) {
// setting nickname
socket.on('setnickname', function(m) {
if(typeof nickname[m] === 'undefined') {
nickname[m] = {count: 0};
socket.emit('nicknamesuccess', m);
} else {
nickname[m].count++;
var t = m + '' + nickname[m].count;
socket.emit('nicknamefail', t);
}
});
// broadcast received message
socket.on('post', function(m) {
console.log(m);
socket.broadcast.emit('msg', m);
})
});
app.listen(80);
訊息傳送的部份很簡單,網頁的處理反而比較複雜
為了節省時間,顯示訊息的部份就用簡單的textarea來做。簡單的事件說明如下:
使用流程規劃成一定要先輸入nickname,設定好nickname才能發送訊息。
來看看網頁端:
<meta charset='utf-8'>
<style>
.container {
font-size: 12px;
border-radius: 10px;
border: solid 1px #336699;
padding: 15px 15px 15px 15px;
line-height: 20px;
width: 400px;
}
.disabled {
color: gray;
}
.enabled {
color: true;
}
</style>
<script src='/socket.io/socket.io.js'></script>
<script src='http://code.jquery.com/jquery-1.8.2.min.js'></script>
<script>
$(document).ready(function() {
var socket = io.connect();
var nickname = '';
$('#form1').submit(function(e) {
e.preventDefault();
socket.emit('setnickname', $('#nickname').val());
});
socket.on('nicknamesuccess', function(m) {
nickname = m;
$('#nickname').prop('disabled', true);
$('#sendnickname').prop('disabled', true);
$('#msg').prop('disabled', false).focus();
$('#send').prop('disabled', false);
$('#msglabel').prop('className', 'enabled');
});
socket.on('nicknamefail', function(m) {
alert('Nickname conflict. Your nickname will be changed to "'+m+'"');
nickname = m;
$('#nickname').val(m);
$('#nickname').prop('disabled', true);
$('#sendnickname').prop('disabled', true);
$('#msg').prop('disabled', false).focus();
$('#send').prop('disabled', false);
$('#msglabel').prop('className', 'enabled');
});
$('#form2').submit(function(e) {
e.preventDefault();
var m = $('#msg').val();
socket.emit('post', {nickname: nickname, msg: m});
$('#msg').val('');
updateMsg({nickname:nickname,msg:m});
})
socket.on('msg', function(m) {
console.log('got msg',m);
updateMsg(m);
});
function updateMsg(msg) {
var ta = $("#panel");
var t = new Date();
var s = t.getHours() + ':' + t.getMinutes() + ':' + t.getSeconds();
var m = '[ ' + msg.nickname + ' (' + s + ')]: ' + msg.msg;
ta.val(ta.val()+'\n'+m);
setTimeout(function(){
ta.scrollTop(ta[0].scrollHeight - ta.innerHeight());
},10);
}
$('#msg').focus();
});
</script>
<div class="container">
<textarea cols='54' rows='24' id='panel' readonly></textarea><br>
<form id='form1' name='form1'>
<label id='nicknamelabel' class='enabled'>Your Nickname: </label><input type='text' size='20' id='nickname'><input type='submit' value='send' id='sendnickname'>
</form>
<form id='form2' name='form2'>
<label id='msglabel' class='disabled'>Message: </label><input type='text' size='54' id='msg' disabled><input type='submit' value='send' id='send' disabled>
</form>
</div>
由於不論是在送出訊息後更新Chat顯示,或是收到廣播訊息後更新Chat顯示,其實作法都差不多,所以統一交給updateMsg函數來處理。由於在這個例子中,Socket跟Http共用相同的網址跟port,所以在連線時甚至不需要輸入網址。
執行畫面看起來會像這樣:
其實要做一個比較完整的Chat,還要考量更多的需求。不過這些到明天再說吧。