首页 WebSocket聊天室

WebSocket聊天室

很早之前就说要写聊天室的教程了,很是尴尬,说这件事的时候已经是去年了,而现在都9月份了,说了那么久,心里很是过意不去,所以现在补上这篇文章。很久没看过有关nodejs相关代码了,所以补上的同时顺带把以前代码重写一下,也捋一捋代码


因为本教程以及我网站上的聊天是使用socket.io写的,而且现在官网已经是2.x了当初写站聊天的时候还是1.4.5版本,所以 如果对NodeJs不懂,可以看看这个教程,个人觉得很不错适合初学者Node入门


效果预览

先看一下效果

  1. 效果一:http://www.zhanghuanglong.com/im/chat/ 也就是网站上的
  2. 效果二:也是本站源码教程image 源码在底部

    运行

运行前需要修改index.html文件中的服务端ip以及mongo.jsmongodb的连接ip

npm 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 端口1202app.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");
})

此部分代码大部分方法中都有注释 简单易懂 所以就讲几个地方

代码讲解 :

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 中国大陆许可协议 进行许可。

评论
目录