很早之前就说要写聊天室的教程了,很是尴尬,说这件事的时候已经是去年了,而现在都9月份了,说了那么久,心里很是过意不去,所以现在补上这篇文章。很久没看过有关nodejs相关代码了,所以补上的同时顺带把以前代码重写一下,也捋一捋代码
因为本教程以及我网站上的聊天是使用socket.io写的,而且现在官网已经是2.x了当初写站聊天的时候还是1.4.5版本,所以 如果对NodeJs不懂,可以看看这个教程,个人觉得很不错适合初学者Node入门
先看一下效果
运行前需要修改
index.html
文件中的服务端ip以及mongo.js
mongodb的连接ipnpm install #安装package.json中的依赖项 node app.js #运行服务端 IP库初始化.. IP库初始化成功! listening on 1202在网页输入
127.0.0.1:1202
即可打开页面 这里没做同一个浏览器打开多个页面也认为是同一个人的判断 需要判断可以自行记录cookie实现工欲善其事,必先利其器
俗话说 “工欲善其事,必先利其器” 所以我们先讲一讲调试nodejs,这里当然是服务端nodejs的调试了,通过
npm
安装调试器devtool
npm install devtool
使用用以下命令 app.js是需要运行的nodejs写的js 剩下的就跟chrome调试js一样了
devtool app.js --watch
源码中有几个文件
app.js
是服务端运行的index.html
浏览器打开的mongo.js
是用来存储聊天记录封装以下是
index.html
的代码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="mian.css" /> </head> <body> <div class="chat-main"> <div class="chat-msg-log"> </div> <div> <input type="text" value="" class="chat-msg" /> <input type="button" value="发送" class="chat-btn" /> </div> </div> <script src="https://cdn.socket.io/socket.io-1.4.5.js"></script> <script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script> <script> var nick = prompt("请输入昵称"); if(nick) {//输入昵称 //连接的ip以及端口 io = new io("192.168.1.197:1202"); //这是昵称 可以记录到cookie中存储 当前已有的昵称 下次打开可以直接判断cookie是否含有nick没有则去让用户输入或者获取 var user = { "nick": nick }; //监听message操作 这里的message是什么呢 ? 能不能乱写呢? io.on("message", function(msg) { console.log(msg) $('<div class="chat-single-msg"><span class="chat-msg-nick">' + msg.FromUser.nick + ':</span> <span class="chat-msg-content">' +msg.Message +'</span></div>').appendTo(".chat-msg-log") }) io.emit("join", user); $(".chat-btn").click(function() { var msg = $(".chat-msg").val(); if(!msg) { return; } io.emit("message", { "message" : msg }, user); }) io.emit("message", { "message" : "进入房间" }, user); } </script> </body> </html>
代码讲解:
- 以下代码: 这是昵称 可以记录到cookie中存储 当前已有的昵称 下次打开可以直接判断cookie是否含有nick没有则去让用户输入或者获取
var nick = prompt("请输入昵称"); var user = { "nick": nick };
io = new io("192.168.1.197:1202");
代表的是ws的地址192.168.1.197
是我本机局域网ip 端口1202
是app.js
中监听的端口io.on
是监听操作io.on("message", function(msg) { console.log(msg) $('<div class="chat-single-msg"><span class="chat-msg-nick">' + msg.FromUser.nick + ':</span> <span class="chat-msg-content">' +msg.Message +'</span></div>').appendTo(".chat-msg-log") })
例如上面的
io.on("message"...
是监听message, 当有服务端发送的消息此方法会捕获 然后把消息append消息记录框中 那这里的message能不能随便乱写呢 还是固定message呢?答案是可以的. 这个message你可以随便输入 只要可服务器约定好 下面服务端代码也可以看到message 下载下面的源码 你可以看到
app.js
文件中有一段代码
//消息 socket.on("message", function(msg, from_user, to_user){ /*console.log(msg); console.log(from_user); console.log(to_user);*/ console.log(msg); sendMessage(socket, msg, from_user, to_user); });
这是服务端在监听message操作 所以只要同时修改这个即可 但是触发的时候也要相对应的修改, 这段代码就是在客户端触发
io.emit('message')
之后可以捕获发送的数据信息
io.emit("message", { "message" : "进入房间" }, user);
io.emit
是触发操作 其中message也是与服务端约定好的 与之对应的服务端app.js
中有一段代码是 socket.emit("message", doc, time, from_user);
这是服务端触发 然后发送给客户端
以上代码 是客户端html页面讲解的, 因为是为了教程新写的所以比较简单 没有按照网站上已有的聊天写
以下是
app.js
代码
var app = require("express")(); var http = require("http").Server(app); var io = require("socket.io")(http); var chat = require("./mongo").chat; app.get("/", function(req, res){ res.sendfile("index.html"); }) var users = [];//所有的用户 var rooms = ["main-room"];//聊天室 io.on("connection", function(socket){ var _user = {}; //console.log("someone connected"); socket.on("join", function(user){ if(user != undefined || user != null || !!user.unique){ //加入main-room房间 socket.join(rooms[0]); //socket.handshake.address //socket.handshake.cookie user.ip = socket.handshake.address.substr(7); user.unread = 0; _user = user; chat.getPublicChatHisOnToday(rooms[0], function(doc){ socket.emit("user_his", { his_message : doc, online_users : getOnlineUser()}); console.log(doc); console.log(users); }) //消息 socket.on("message", function(msg, from_user, to_user){ /*console.log(msg); console.log(from_user); console.log(to_user);*/ console.log(msg); sendMessage(socket, msg, from_user, to_user); }); //撤回消息 socket.on("revoke message", function(msg_id, from_user){ revokeMessage(socket, msg_id, from_user); }) //获取私聊的历史消息 socket.on("get private his msgs", function(with_user, from_user){ getPrivateChatHis(socket, with_user, from_user); }) broadcast(socket, user); } }); socket.on("disconnect", function(){ if(removeuser(_user, socket)){ //删除成功之后广播这个人下线了 socket.to(rooms[0]).broadcast.emit('leave', _user) } }); //触发连接事件 socket.emit("connection"); }); /** * 获取私聊的今天聊天记录 * @param {Object} socket * @param {Object} with_user * @param {Object} from_user */ function getPrivateChatHis(socket, with_user, from_user){ chat.getPrivateChatHisOnToday(rooms[0], with_user.unique, from_user.unique, function(doc){ socket.emit("get private his msgs", with_user, doc); }) } /** * 撤销消息 * @param {Object} socket * @param {Object} msg_id 消息id * @param {Object} from_user 撤销用户 */ function revokeMessage(socket, msg_id, from_user){ chat.revokeMessage(msg_id, from_user.unique , function(isRevoke){ if(isRevoke){ socket.emit("revoke message", msg_id, from_user, new Date().getTime()); socket.to(rooms[0]).broadcast.emit('revoke message', msg_id, from_user , new Date().getTime()); } }) } /** * 发送消息 * @param {Object} socket * @param {Object} message * @param {Object} from_user * @param {Object} to_user */ function sendMessage(socket, message, from_user, to_user){ if(!message || /<([a-zA-Z\/]+)[^>]*>|<script[^>]*>/.test(message)){ return; } var time = Date.now(); console.log(time); chat.insertMessage2His(rooms[0], message.message, message.type, from_user, to_user, socket.handshake.address.substr(7), 1, function(doc){ socket.emit("message", doc, time, from_user); if(!to_user){ socket.to(rooms[0]).broadcast.emit('message', doc, time, from_user); }else{ var sockets = getSocketsByUser(to_user); for (var i = 0; i < sockets.length; i++) { sockets[i].emit("message", doc, time, from_user, to_user); } } }) } /** * 广播新用户进入房间 以user.unique中的为一个用户而不是一个页面一个用户 * @param {Object} socket * @param {Object} user */ function broadcast(socket, user){ if(user == null || !user.unique){ return; } var has = false; for (var i = 0; i < users.length; i++) { if(users[i].user.unique == user.unique){ has = true; users[i].sockets.push(socket); break; } } if(!has){ console.log("进入房间"); console.log(user); users.push({user : user, sockets : [socket]}); socket.to(rooms[0]).broadcast.emit('join', user) } } /** * 用户不在线删除用户 * @param {Object} user * @param {Object} socket */ function removeuser(user, socket){ console.log(socket.id); var index = -1; for (var i = 0; i < users.length; i++) { if(users[i].user.unique == user.unique ){ if(users[i].sockets.length <= 1){ index = i; }else{ console.log(users[i].sockets.length); var idIndex = -1; for (var socketIndex = 0; socketIndex < users[i].sockets.length; socketIndex++) { if(users[i].sockets[socketIndex].id == socket.id){ idIndex = socketIndex; break; } } if(idIndex > -1){ console.log(idIndex); users[i].sockets.splice(idIndex, 1); } } break; } } if (index > -1) { console.log("离开房间:"); console.log(user); users.splice(index, 1); return true; } return false; } /** * 根据user获取对应的所有的socket * @param {Object} user user */ function getSocketsByUser(user){ for (var i = 0; i < users.length; i++) { if(users[i].user.unique == user.unique ){ return users[i].sockets; } } return []; } /** * 获取在线的所有的用户 */ function getOnlineUser(){ var us = []; for (var i = 0; i < users.length; i++) { us.push(users[i].user); } return us; } //在端口1202监听 http.listen(1202, function(){ console.log("listening on 1202"); })
代码讲解 :
- 以下代码 上半部分是当用户在浏览器输入
http://127.0.0.1:1202/
返回index.html页面 下半部分可以认为 在监听1202这个端口 所以才能打开http://127.0.0.1:1202/
地址
app.get("/", function(req, res){ res.sendfile("index.html"); }) //在端口1202监听 http.listen(1202, function(){ console.log("listening on 1202"); })
var rooms = ["main-room"];//聊天室
此代码原本打开写多个聊天室的 但是后来没写 所以可以看到以下代码socket.join(rooms[0]);//加入main-room房间
所以就在服务端直接加入一个房间 如果需要加入不同的房间 则可以考虑两个 一是通过url地址来区别当前的room
是哪个 二是在客户端js里写入房间这里简单的说一下思路
客户端; io.emit("join", user, roomName);//指定房间号 服务端: socket.on("join", function(user, roomName){//对应的多了一个参数 socket.join(roomName); }
虽然以上代码没问题 但是问题就是js是可以随意修改的 所以可以通过方法一url来确定房间 如果url都被修改直接可能都打不开网页了所以修改也无所谓 然后url对应的房间存储在服务器端
- 另一个需要注意的是
广播
和私聊
的触发以下是app.js
中某句代码
socket.to(rooms[0]).broadcast.emit('message', doc, time, from_user);
这句代码就是对room[0]对应的房间进行广播 即所有人聊天 也可以说是公聊
而以下代码就是私聊
var sockets = getSocketsByUser(to_user); for (var i = 0; i < sockets.length; i++) { sockets[i].emit("message", doc, time, from_user, to_user); }
注意看的话 可以发现和广播的虽然触发方式一样 但是触发的对象是不同的 而这里的getSocketsByUser 是每当有用户join进来的时候就会往
var users = [];//所有的用户
申明的对象users添加 所以我们可以查询到某个人 然后进行私聊发送消息
mondo.js
里面好像都是一些封装的方法 方法都有注释很好理解也就不说了 唯一需要注意的是运行的时候需要修改mongo所在的ip var url = "mongodb://192.168.1.199:27017/Resources";
教程里写出来的比较粗略,但是功能都封装好了。。只是没有在本教程写出来,
教程源码 : https://git.kerwin.cn/Shares/WebSocketChatWithSocketIO.git
除另有声明外,本文章WebSocket聊天室采用 知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议 进行许可。