天天看點

nodejs 基于socket.io實作聊天室

由于之後要做的網頁視訊直播項目要用到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聊天室

繼續閱讀