由于之後要做的網頁視訊直播項目要用到socket.io子產品,是以特地花時間研究了下,參照網上的代碼做了些改進,自己寫了個聊天室代碼。不得不承認後端事實推送能力有點厲害,這是以前我用php一直苦惱的事情。下面簡單介紹下我的項目,順帶講解下nodejs。
事實上,在看别人寫的代碼之前,我一直不知道nodejs是幹嘛的,直到真正接觸到才明白這也可以算作是服務端代碼,豐富的第三方庫使其功能極其強大。它可以像golang的beego一樣直接通過指令行開啟伺服器,不過要用到express子產品。加載子產品的方式類似php,如下:
var express = require('express'); //引用express
要想開啟一個伺服器隻需以下幾行代碼即可:
var express = require('express'); //引用express
var app = express();
var server = require('http').Server(app);
//監聽伺服器啟動
server.listen(3000, function() {
console.log("Express server listening on port " + app.get('port'));
});
在伺服器開啟成功後用console.log在指令行列印消息,及其友善。
接下來就該定義路由了,事實上nodejs有路由子產品,然而我還沒研究過那子產品具體實作了哪些功能,隻借用了别人的代碼來寫基本的路由,大緻形式如下:
app.set('views', __dirname + '/views');
app.get('/', function(req, res) {
//res.send('hello world');
res.sendFile(app.get("views") + '/login.html');
//res.redirect('/login');
});
req即request請求,res即response傳回,這與javaweb有些類似。res.send大概是直接輸出字元串内容到頁面上,而sendFile則是将指定檔案的内容輸出到頁面,redirect即重定向。倘若我在3000端口開啟伺服器,那麼以上代碼則規定當我通過get方法通路localhost:3000/時,将views/login.html的内容輸出到頁面上,其實也就是通路了views/login.html這個頁面。app.get中的get即get方法。同樣,若要寫post請求的接口,則用app.post即可。 注:這個app對象即文章開始定義的express對象。
介紹完路由,再來介紹模闆。既然nodejs能作為服務端語言來開發,那麼自然少不了模闆子產品。當然,nodejs有很多模闆子產品,這邊我隻了解了ejs子產品,就先隻介紹這。
調用方式如下:
app.set("view engine", "ejs");
//聊天室首頁
app.get('/index', function(req, res) {
res.render("index", {
"user": req.session.user
});
});
調用res.render方法來傳值到頁面,第一個參數即模闆頁面的名稱即views/index.ejs(注意字尾是ejs),第二個參數是附帶的資料,既然是js,那附帶的資料自然是js下的json資料。 注:在nodejs中views和view似乎都有規定,定義頁面路徑時app.set('views', __dirname + '/views');程式運作成功,而app.set('view', __dirname + '/views');則會報錯,錯誤原因忘了,反正我檢視了英文論壇下的答複才知道是這個問題,定義頁面路徑時最好還是用views吧。
在模闆頁面調用也很友善,直接<%=user%>即可。如下:
<input type="hidden" value="<%=user%>" id="user" />
req.session.user是我使用了session子產品。因為我的聊天室做了登陸頁的,順便用了下session子產品熟悉熟悉。調用方式如下:
var session = require('express-session'); //如果要使用session,需要單獨包含這個子產品
app.use(session({
secret: 'ScumVirus',
name: 'sv_chat', //這裡的name值得是cookie的name,預設cookie的name是:connect.sid
cookie: {
maxAge: 3600000
}, //設定maxAge是3600000ms,即1h後session和相應的cookie失效過期
resave: false,
saveUninitialized: true,
}));
//設定session
req.session.user = "ScumVirus";
//擷取session
var name = req.session.user;
nodejs還提供了加密子產品,可以很友善的加密字元串,我一般都用32位md5加密,隻介紹擷取32位md5加密的方法,如下:
var crypto = require('crypto'); //加密
//擷取md5加密後的值
var getMD5 = function(str) {
var md5 = crypto.createHash('md5');
md5.update(str);
var d = md5.digest('hex');
return d;
}
另外我還用到了mysql子產品,用于連接配接mysql資料庫,實作前台注冊登陸,調用如下:
var mysql = require("mysql"); //資料庫子產品
//連接配接資料庫
var connPool = mysql.createPool({
host: '127.0.0.1', //主機
user: 'root', //MySQL認證使用者名
password: 'admin', //MySQL認證使用者密碼
port: '3306', //端口号
database: 'sv_chat', //資料庫
waitForConnections: true, //當連接配接池沒有連接配接或超出最大限制時,設定為true且會把連接配接放入隊列
//connectionLimit:10,//連接配接數限制
});
//根據使用者擷取使用者資訊
var getUserById = function(name, callback) {
//執行SQL語句
var sql = 'select * from sv_user where name=?';
var params = [name];
connPool.query(sql, params, function(err, result) {
if (err) {
console.log('[SELECT ERROR] - ', err.message);
return;
}
return callback(result[0]);
});
}
//添加新使用者
var addUser = function(params, callback) {
//執行SQL語句
var sql = 'insert into sv_user(`name`,`pwd`,`email`,`phone`,`create_time`) values(?,?,?,?,?)';
connPool.query(sql, params, function(err, result) {
if (err) {
console.log('[INSERT ERROR] - ', err.message);
return callback(false);
} else {
console.log('INSERT ID:', result.insertId);
return callback(true);
}
});
}
這邊有興趣的話可以自己列印下查詢的結果result,是json格式的資料,有insetId,也有affectedRows,同php幾乎一樣。不過唯一坑爹的一點是,查詢資料庫是異步執行的,若要将查詢結果傳回給前台頁面,可能值一直擷取不到,這裡必須善用回調方法。網上也有介紹說可以線性執行,但是我還沒仔細研究過,回調我了解的快,便先用回調函數處理了。
最後是最關鍵的通信階段,socket.io子產品,背景代碼如下:
var io = require('socket.io').listen(server); //socket io子產品
//WebSocket連接配接監聽
io.on('connection', function(socket) {
//socket.emit('open',onlineMember); //通知用戶端已連接配接
// 列印握手資訊
// console.log(socket.handshake);
// 構造用戶端對象
var client = {
name: '',
}
// 對message事件的監聽
//登入事件
socket.on('login', function(name) {
var time = getTime();
client.name = name;
var index = getArrIndex(name,onlineMember);
if(index == -1){
onlineMember.push(client.name);
console.log(time + " " + client.name + " login");
}
var obj = {
time: time,
author: client.name,
text: '',
type: 'login',
member: onlineMember
};
socket.emit('system', obj);
socket.broadcast.emit('system', obj);
});
//消息事件
socket.on('message', function(msg) {
var obj = {
time: getTime(),
};
obj['msg'] = msg;
obj['author'] = client.name;
obj['type'] = 'message';
// 傳回消息(可以省略)
socket.emit('message', obj);
// 廣播向其他使用者發消息
socket.broadcast.emit('message', obj);
});
//監聽退出事件
socket.on('disconnect', function() {
var index = getArrIndex(client.name, onlineMember);
if (index > -1) {
onlineMember.splice(index, 1);
}
var time = getTime();
var obj = {
time: time,
author: client.name,
text: '',
type: 'loginout',
member: onlineMember
};
console.log(time + " " + client.name + " loginout");
// 廣播使用者已退出
socket.broadcast.emit('system', obj);
});
});
前台連接配接代碼:
<script src="/socket.io/socket.io.js"></script>
<script>
//建立websocket連接配接
socket = io.connect('http://localhost:3000');
var userName = $("#user").val();
socket.emit('login', userName);
</script>
socket.emit可以了解成雙向通信的方法,若用戶端A與服務端B連接配接上了,那麼A調用emit方法,則B收到消息,B調用emit方法,則A收到消息。而socket.broadcast.emit方法則是廣播,若用戶端A,B,C同時連接配接上服務端D,A通過emit發送消息給D,D接收消息後調用廣播方法,則B,C收到消息,而A則收不到消息。login是我自定義的推送消息類型,message是插件本身就定義了的類型,前台可通過socket.send(msg)觸發,,disconnect也是插件定義的類型,在用戶端斷開連接配接時調用。
最後放上我源代碼的下載下傳位址:nodejs聊天室