很早之前就说要写聊天室的教程了,很是尴尬,说这件事的时候已经是去年了,而现在都9月份了,说了那么久,心里很是过意不去,所以现在补上这篇文章。很久没看过有关nodejs相关代码了,所以补上的同时顺带把以前代码重写一下,也捋一捋代码
因为本教程以及我网站上的聊天是使用socket.io写的,而且现在官网已经是2.x了当初写站聊天的时候还是1.4.5版本,所以 如果对NodeJs不懂,可以看看这个教程,个人觉得很不错适合初学者Node入门
先看一下效果
运行前需要修改
index.html文件中的服务端ip以及mongo.jsmongodb的连接ipnpm install #安装package.json中的依赖项 node app.js #运行服务端 IP库初始化.. IP库初始化成功! listening on 1202在网页输入
127.0.0.1:1202即可打开页面 这里没做同一个浏览器打开多个页面也认为是同一个人的判断 需要判断可以自行记录cookie实现工欲善其事,必先利其器
俗话说 “工欲善其事,必先利其器” 所以我们先讲一讲调试nodejs,这里当然是服务端nodejs的调试了,通过
npm安装调试器devtoolnpm 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 中国大陆许可协议 进行许可。