1、回顧上一集內(nèi)容
- 在本地安裝和配置node環(huán)境
- 用node創(chuàng)建http服務(wù)器,配置路由,安裝所需的模塊
- 前后端都引入socket.io
- 根據(jù)socket.io提供的api進(jìn)行事件的傳遞
上一集內(nèi)容已經(jīng)實(shí)現(xiàn)了用戶加入到一個(gè)公共的空間,并且把對(duì)應(yīng)的socketID給打印到前端頁(yè)面上。
這一集內(nèi)容將會(huì)帶大家一起實(shí)現(xiàn)可以實(shí)時(shí)發(fā)送文字流的socket聊天室,為了給后續(xù)的課程做鋪墊,這里會(huì)先講一下用戶登錄功能的實(shí)現(xiàn)。
2、用戶登錄
數(shù)據(jù)庫(kù)方面筆者這里用的mongodb,所以下面的介紹也是圍繞mongodb來(lái)介紹的
1)安裝mongodb以及robomongo
下載地址
mongodb:https://www.mongodb.com/download-center
robomongodb(mongodb可視化工具):https://robomongo.org/
配置(以win7為例)
- 配置全局環(huán)境
筆者的mongo安裝路徑:D:/mongoDB/
將D:/mongoDB/bin添加到全局環(huán)境,按下圖從左到右紅框點(diǎn)擊順序,將路徑添加到path變量值后。
Paste_Image.png
- 讀取配置并自動(dòng)啟動(dòng)
新建文件夾
D:/mongodbData/ --- 存放log和db,日志數(shù)據(jù)和數(shù)據(jù)庫(kù)數(shù)據(jù)
D:/mongodbData/log --- 存放日志數(shù)據(jù)
D:/mongodbData/db --- 存放數(shù)據(jù)庫(kù)數(shù)據(jù)
D:/mongodbData/mongodb.cfg --- 存放數(shù)據(jù)庫(kù)啟動(dòng)的配置
更改配置(寫入mongodb.cfg)
dbpath=D:/mongodbData/db
logpath=D:/mongodbData/log/mongodb.log
auth=true #開(kāi)啟認(rèn)證
安裝mongodb為window Service服務(wù)(安裝完在services.msc可以找到mongodb的服務(wù))
$ mongod --config "D:/mongodbData/mongodb.cfg" --install --serverName mongodb
嘗試
$ net start mongodb #啟動(dòng)
$ net stop mongodb #關(guān)閉
- 用戶認(rèn)證
由于上面cfg文件中auth=true,開(kāi)啟了用戶認(rèn)證,所以連接數(shù)據(jù)庫(kù)后必須通過(guò)認(rèn)證才能執(zhí)行敏感操作。未登錄時(shí)進(jìn)行敏感操作會(huì)出現(xiàn)下圖情況。所以我們需要先創(chuàng)建頂級(jí)用戶
Paste_Image.png
依次輸入下面的命令
$ mongo #連接mongo服務(wù)器
$ use admin #使用admin數(shù)據(jù)庫(kù)
$ db.createUser({ #創(chuàng)建用戶root,擁有root權(quán)限
user:"root",
password:"123456",
roles:[{role:"root",db:"admin"}]
})
$ db.auth("root","123456") #通過(guò)認(rèn)證
$ use demo #新建mydb數(shù)據(jù)庫(kù)
$ db.createUser({ #創(chuàng)建普通用戶ze,擁有讀寫權(quán)限
user:"ze",
pwd:"123456",
roles:[{role:"readWrite",db:"demo"}]
})
下一次使用命令行登錄時(shí)
$ mongo -uze -p123456 demo
使用robomongo進(jìn)行登錄時(shí)
Paste_Image.png
2)node安裝mongodb依賴
$ npm i mongoose --save-dev #一個(gè)mongodb的框架
在原先項(xiàng)目目錄新建mongo.js,寫入
var mongo = require('mongoose');
mongo.connect('mongodb://ze:123456@localhost:27017/demo');
var db = mongo.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.on('connected', function (callback) {
console.log("db has already opened");
});
db.on('disconnected', function (callback) {
console.log("db has already exited");
});
var Schema = mongo.Schema; // 配置每一條記錄的模型
var UserSchema = new Schema({
account : { type: String }, //賬號(hào)
password: {type: String} // 密碼
});
module.exports = mongo.model('userlist',UserSchema);
在socket.js中引用
var mongo = require('./mongo');
命令行啟動(dòng)socket.js,出現(xiàn)下圖則已經(jīng)成功連接數(shù)據(jù)庫(kù)
Paste_Image.png
3)以登錄功能為例做分析
- 前端(登錄會(huì)存用戶名到cookie)
var data = {
username: $( "input#username" ).val(),
password: $( "input#password" ).val()
};
$.post( "/login", data, function( data, status ) {
switch( data.result ) {
case 1: // 與后端協(xié)定好,1代表登錄成功
// 將用戶名存入cookie
// 連接到socket服務(wù)器,同時(shí)發(fā)送login事件
var socket = io.connect();
// Your_username是從cookie獲取
socket.emit( "login", Your_username );
default:
// 打印無(wú)法成功登錄的信息;
alert( data.msg );
}
});
// 捕獲login事件后,執(zhí)行回調(diào)
socket.on("login",function( names ){
var str = "";
names.forEach( function( item, index) {
str += item + "<br/>";
})
$( "#curUser" ).html( str );
})
- 后端(路由設(shè)置和socket事件響應(yīng))
路由設(shè)置(登錄)
server.post('/login', function(req, res) {
var data = []; // 存放post數(shù)據(jù)
req.on("data", function(chunk) {
data.push(chunk); // 前端post方式發(fā)過(guò)來(lái)的數(shù)據(jù)都是轉(zhuǎn)成buffer格式再發(fā)給后端的
});
req.on("end", function() {
// console.log( data ); --- 可以看到buffer類型數(shù)據(jù)
// 獲取post請(qǐng)求信息
var postData = Buffer.concat(data).toString();
// console.log( postData ); --- 這里通過(guò)格式轉(zhuǎn)換成了url參數(shù)格式
var loginInfo = querystring.parse(postData);
// console.log( loginInfo ); --- 這里就是將url參數(shù)轉(zhuǎn)換成json格式
var usr = {
account: loginInfo.account,
password: loginInfo.password
};
// mongoose的查找方法,result返回一個(gè)數(shù)組,包含查找的信息
mongo.find(usr, function(err, result) {
var response;
if (err) {
console.log(err);
} else {
if (result.length) {
response = {
result: 1,
msg: "登錄成功",
account: loginInfo.account
};
} else {
response = {
result: -1,
msg: "用戶或者密碼不正確"
};
}
res.end(JSON.stringify(response));
};
});
});
});
socket事件響應(yīng)
var names = [];
io.on('connection', function(socket) {
socket.on('login',function(name){
names.push( name );
io.sockets.emit('login',names);
})
});
3、文字聊天功能實(shí)現(xiàn)
- 這個(gè)功能實(shí)現(xiàn)的關(guān)鍵在于后端,所有的信息都是通過(guò)后端進(jìn)行接收和轉(zhuǎn)發(fā)的,舉個(gè)例子,clientA想向clientB發(fā)一條信息,那么需要告訴后端,clientB的地址以及發(fā)送的信息。關(guān)于clientB的地址,我們可以在每個(gè)客戶端登錄到socket服務(wù)器的時(shí)候就將客戶端的socketName和socket地址存起來(lái),那么只需要知道socketName就可以遍歷出socket地址,就可以進(jìn)行點(diǎn)對(duì)點(diǎn)信息交換了。
- 接下來(lái)演示一下前后端的關(guān)鍵代碼,這里演示的是信息群發(fā),不針對(duì)某個(gè)客戶端,只要登錄到socket服務(wù)器的客戶端都能接收到
前端輸入信息,點(diǎn)擊發(fā)送:
// 定義信息呈現(xiàn)出來(lái)的模板
var msgTpl = "<li><span class='user'>{username}</span><span class='msg'>{msg}</span></li>";
// 點(diǎn)擊發(fā)送
$( ".btn-send" ).on( "click", function( e ) {
// 信息輸入框
var msg = $( "input.message" );
// username可以從cookie獲取,也可以存入一個(gè)變量
socket.emit( "message", username, msg.val() );
updateMsg( username, msg.val() );
// 發(fā)送完將輸入框的值清空
msg.val( "" );
});
function updateMsg( username, msg ) {
var msgContainer = $( ".msgContainer" );
var msg = {
username: username,
msg: msg
};
// 最近看到的用正則全局替換模板,比之前的html拼接帥多了
for( var key in msg ) {
var reg = new RegExp( "\\{" + key + "\\}", "g" );
msgTpl = msgTpl.replace( reg, msg.key );
}
msgContainer.append( msgTpl );
}
// 當(dāng)收到別的客戶端發(fā)的信息時(shí)執(zhí)行的回調(diào)
socket.on( "message", function( username, msg ) {
updateMsg( username, msg );
})
后端進(jìn)行消息轉(zhuǎn)發(fā):
socket.on('message', function(username, msg) {
// socket.broadcast --- 連接同一個(gè)socket服務(wù)器,并且同一個(gè)namespace,除了自己以外的其他用戶
socket.broadcast.emit('message', username, msg );
});
注:對(duì)于namespace和room有疑問(wèn)的參考下方鏈接
http://blog.csdn.net/lijiecong/article/details/50781417
4、小結(jié)和預(yù)告
- 用戶登錄,前后端和數(shù)據(jù)庫(kù)如何交接
- 消息轉(zhuǎn)發(fā),其實(shí)就是socket事件驅(qū)動(dòng),類似的實(shí)現(xiàn)方法還有h5的新接口eventSource(SSE),一樣可以實(shí)現(xiàn)事件驅(qū)動(dòng)來(lái)推送信息,但是一般不做聊天用。
- 預(yù)告下一期介紹基于webRTC的視頻聊天室。大家都用flash做視頻聊天,可是我又不會(huì)actionScript,能用JS寫個(gè)視頻聊天室嗎?能!
————
前端·小澤
給點(diǎn)信心自己,就一定能做到