由于之后要做的网页视频直播项目要用到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聊天室
版权声明:本文为博主原创文章,未经博主允许不得转载。