使用Socket.IO仿微信聊天应用

页面布局的构建

根据网页版的微信,利用flex框架进行重建。手机版根据实际情况将聊天页面隐藏,点击相应用户的聊天室显示相应的聊天页面。

socket.io

基本原理

  • 是一个开源的WebSocket库,它通过Node.js实现WebSocket服务端,同时也提供客户端JS库。
  • 原理是基于事件的推送实现双向通讯。对于数据的处理就是一个个的事件触发。

基本语法

// send to current request socket client
socket.emit('message', "this is a test");
// sending to all clients except sender
socket.broadcast.emit('message', "this is a test");
// sending to all clients, include sender
io.sockets.emit('hi', 'everyone');
// sending to individual socketid
io.sockets.socket(socketId).emit('message', 'for your eyes only');

服务端代码实现

var server = http.createServer(app);
//// 在线用户名单
var onlineList = [];
// 创建socket服务
var io = require('socket.io').listen(server);
io.on('connection', function(socket) {
  var usr;
  //加入聊天室
  socket.on('online', function(uid) {
    //用户加入
    usr = uid;
    socket.join(usr);
    //把当前用户加入在线用户名单中
    if ( onlineList.indexOf(uid) === -1) {
      onlineList.push(uid);
    }
    console.log(usr + ' online...');
  });

  // 处理用户消息
  socket.on('message', function (uid,fid,msg) {
    var type;
    // 判断接收方是否在线
    if (onlineList.indexOf(fid) === -1) {
      //不在线将消息保存到数据库
      type = config.site.OFFLINE;//OFFLINE是1
      io.to(uid).emit('msg', uid, fid, msg);//user
    }else{
    //接收方在线,将消息发送给对方
      type = config.site.ONLINE;
      io.to(fid).emit('msg', uid, fid, msg);//(_id,fid,msg);
      io.to(uid).emit('msg', uid, fid, msg);
    }

    var data = {
      "uid":uid,
      "from":uid,
      "to":fid,
      "type":type,
      "msg":msg
    };
    console.log(data);
    //数据库存入
    dbHelper.addMsg(data, function (success, data) {
      console.log(uid + ' -> ' + fid + 'msg saved...');
    })
  });

   //非正常下线
     socket.on('disconnect', function() {
       // 从在线名单中移除
       var index = onlineList.indexOf(usr);
       if (index !== -1) {
         onlineList.splice(index,1);
         socket.leave(usr);
       }
       console.log(usr + ' offline...');
     });

  //正常下线
  socket.on('leave', function() {
    socket.emit('disconnect');
  });

});

客户端代码

<section class="chat">
    <nav>
        //显示当前用户的基本消息
        <div class="header">
            <img class="avatar" src="{{user.userThumb}}">
            <h3 class="nickname">
                {{user.username}}
            </h3>
        </div>
        //搜索用户
        <div class="search_bar">
            <i class="wechat_search"></i>
            <input class="frm_search" type="text"  placeholder="搜索">
        </div>
        //功能选择
        <ul id="myTab" class="nav nav-tabs tab">
            <li class="active">
                <a class="" title="好友列表" href="#chat" data-toggle="tab">
                    <i class="web_wechat_chat"></i>
                </a>

            </li>
            <li>
                <a class="" title="传输文件" href="#read" data-toggle="tab">
                    <i class="web_wechat_public" ></i>
                </a>

            </li>
            <li>
                <a class=""  title="用户列表" href="#user" data-toggle="tab">
                    <i class="web_wechat_friends" ></i>
                </a>

            </li>
        </ul>
        //好友列表
        <div id="" class="chats tab-content">
            <div class="tab-pane fade in active" id="chat">
                <div class="wrapper-content">
                    <section class="list">
                        </section>
                    </div>
            </div>
            <div class="tab-pane fade" id="read">
            </div>
        //获取数据库用户列表
        <div class="tab-pane fade" id="user" data-button="addFrd">
             {{#each entries}}
                 <p data-toggle="select" data-id="{{_id}}" class="active chat_item">
                    <img class="avatar" src="{{userThumb}}">
                       <div class="info">
                        <span class="nickname_text">{{username}}</span>
                        </div>
                        <div class="ext">
                            <a href='#' data-id="{{_id}}" class='addFriendBtn'>添加</a>
                        </div>

                {{/each}}
               </div>
            </div>
</nav>
    //聊天区域
    <div id="chatArea" data-id="">
        <div class="title_wrap">
            <a class="friend" data-id=""></a>
            <div class="message-content">
                <div  class="box-content">
                    hello
                </div>
            </div>
            <form action="">

                <div class="box_ft box-send">
                    <div class="toolbar">
                        <a class="web_wechat_face"  title="表情"></a>
                        <a  class="web_wechat_screencut" href="" title="截屏"></a>
                        <a  class="web_wechat_pic" title="图片和文件"></a>
                    </div>
                    <div class="content">
                        <input class="chat-input input-ctn" id="editArea">

                    </div>
                    <div class="action">
                        <span class="desc ng-scope">按下enter发送</span>
                    </div>
                </div>
            </form>
        </div>
    </div>
</section>
<script src="/chat/chat.js"></script>
<script src="/chat/common.js"></script>
<script src="/lib/socket.io/socket.io.js"></script>

上面的html内容本身没有什么好说的,我们主要看看里面的4个文件请求
1./lib/socket.io/socket.io.js
2.common.js
3.chat.js
第1个JS是Socket.IO提供的客户端JS文件,当npm安装完socket.io并搭建起WebServer后,这个JS文件就可以正常访问了
第2个common.js主要提供一些格式处理,路径获取,消息提示这里就不再赘述
第3个chat.js是完整的客户端的业务逻辑实现代码,它的内容如下:

$(init);

function init() {
  initSocket();
  initFriendList();

  $('body').on('click', '.addFriendBtn' , doAddFreind);
  $('body').on('click', '.wrapper-content .list li' , doChatSession);
  $('body').on('keydown', '#editArea' , doSend);
}
//初始化socketio
function initSocket() {
  socket = io();
  socket.emit('online', _id);

  // 监听消息
  socket.on('msg', function(uid, fid, msg) {
    var fromID = (_id == fid)?uid:fid;
    var message;
    //当前用户如果是好友的话显示在发送方
    if (_id == fid) {
      console.log(uid+"发送信息给"+fid );
      friendThumb = $('#'+uid).children('img').attr('src');
      //前端显示接收信息
      var html = '<li class="message you"><img class="avatar" src="friendThumb"><p class="you">' +msg +'</p></li>';
        $('#messages').append(html);
    } else {
        //前端显示发送消息
       var html = '<li class="message me"><img class="avatar" src="$.cookie('userThumb')><p class="you">' +msg +'</p></li>';
        $('#messages').append(html);
      console.log("from");
    }
  });
}

//计算sessionid将较大id放置前面小的加后面,这样两个人聊天的sessionid就是唯一的
function calcuSessionId(uid,fid) {
  return (uid>fid)?(uid+fid):(fid+uid);
}
//新建会话过程
function doChatSession() {
  fid = $(this).attr("id");
  //得到sessionid
  var sessionId = calcuSessionId(_id,fid);
  console.log(sessionId);
  //第一次点击的话,发送初始化session消息
  if (chatSession.indexOf(sessionId) === -1) {
    chatSession.push(sessionId);
    socket.emit('join', sessionId);
  }
  //切换界面
  toggleChatView(fid);
}

//切换聊天窗口
function toggleChatView(fid) {
  if ($("#message"+fid).length == 0) {
    $(".title_wrap .message-content").prepend('<div class="box-content" id="message'+fid+'"></div>');
  }
  $(".box-content").hide();
  $("#message"+fid).show();
}

//发送消息给朋友
function doSend(e) {
  if (e.which  === 13) {//获取enter键
    e.preventDefault();
    var msg = $(this).val();
    console.log(msg);
    $(this).val('');
    // 发送消息
    socket.send(_id,fid,msg);
  }
}

最近入的坑

  • format的constructor说undefined,通常是自己传进的参数与定义的的不一致,该函数本身是没错的
  • 加入session支持,认证机制必须依赖cookie,所以还应该同时安装一个cookie-parser,然后再app.js中导入这两个中间件,
    定义cookie解析器,注意,该定义必须写在路由分配之前.
  • 用户头像的显示
    在route文件夹下添加user: req.session.user,这样相应页面就可以调用user内的数据,通过{{user.userThumb}}和`{{user.username}}显示用户名及头像
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容