zoukankan      html  css  js  c++  java
  • node基于express的socket.io

    前一段事件,我一个同学给他们公司用融云搭建了一套web及时通信系统,然后之前我的公司也用过环云来实现web及时通信,本人对web及时通信还是非常感兴趣的。私下读了融云和环信的开发文档,然后发现如果注册第三方貌似开发群聊就是个问题了(因为自己做个两个人聊天很简单,想做的在复杂些就要花钱了- -orz),包括后期我想开发表情包,群聊,和消息加密等等。本着节省的原则尝试去看了一下socket.io,结果一发不可收拾。。。好了闲话少说,今天来分享一份关于socket.io的东西!!

    注:本篇文章建议有node基础的同学看,最好有一些express的开发经验

    首先我们搭建了一个express的项目,我直接用了express的默认jade,小伙伴们喜欢ejs就自己创建成ejs模版的项目!

    之后我们引进socket,代码如下:

    {
      "name": "jadeShop",
      "version": "0.0.0",
      "private": true,
      "scripts": {
        "start": "node ./bin/www",
        "dev": "supervisor ./bin/www"
      },
      "dependencies": {
        "body-parser": "~1.15.1",
        "cookie-parser": "~1.4.3",
        "debug": "~2.2.0",
        "express": "~4.13.4",
        "mysql":"*",
        "jade": "~1.11.0",
        "morgan": "~1.7.0",
        "serve-favicon": "~2.3.0",
        "socket.io": "*"
      }
    }

    这个是我的package.json的配置,我安装了supervisor,,然后mysql(因为之前做过一段时间php开发所以本人还是比较喜欢mysql,如果喜欢nosql的小伙伴么,自己安装mongodb)和socket.io之后直接进入项目运行npm install自动加载包依赖。

    我们加载了socket.io之后我们进入到app.js来建立socket.io的connect,代码如下

    var users = {}
    var server = http.createServer(app)
    var io = require('socket.io').listen(server)

    我们的users用来存所有的在线用户,第二行的app是express框架自动生成。之后我们的io就是socket的实力,我们可以调用io下的socket.on方法

    代码如下:

    io.sockets.on('connection', function (socket) {
    
    }

    这样每当我们在iterm上启动服务器那么socket.io就会自动连接,然后我们方法里面有一个回调函数,socket这个参数我们就可以去on自己想要的事件了,这里的事件是自定义名称,你可以叫a,也可以叫b,但是最好语意话一些!

    我们来说一下我们用的几个基本方法,因为socket.io是消息推送,而不像ajax长轮询那样子。所以我们肯定是需要一个方法去坚挺客户端发送的方法,这里不管是服务端还是客户端其实都是一个原理,然后我们还需要一个方法去推送数据!

    监听方法代码如下:

    socket.on('online', function (data) {}

    推送方法如下代码:

    1.向所有人推送:

    io.sockets.emit('online', {users: users, user: data.user})

    2.向客户端某个人推送:

    var clients = io.sockets.clients()
    clients.forEach(function (client) {
        if (client.name === data.to) {
           client.emit('say', data)
        }
    })

    这里我们的clients就是所有在线用户,他是一个数组我们forEach来遍历这个数组,然后判断当我们的数组里面的某个人是你想发送的人,直接emit,这里也是一个回调函数。

    3.向除了自己外的所有人推送:

    socket.broadcast.emit('say', data)

    好了基本上有这些方法我们就能做东西了,这些基本上就是服务端socket.io用的所有东西了。

    太他妈太简单了,闲话少说,直接说客户端。

    我们需要引入一些文件:

    script(src='javascripts/layout/jquery.cookie.js')
    script(src='/socket.io/socket.io.js')

    注:我用的是jade模版,所以这么写

    我们这里用到了jquery的cookie,这个看自己需求,也可以自己直接写原声js,然后我们引入socket.io我们就能用啦,是不是很简单?

    然后我们来写客户端的代码:

    var socket = window.io.connect() 
    var from = window.$.cookie('user')

    socket实例和node端基本上一个用法,from是我们从cookie取出来的登录用户。

    客户端不需要服务端那样好多发送,所以直接emit就可以了哈哈

    socket.emit('online', {user: from})
    socket.on('online', function (data) {
      console.log(data)
    })

    现在代码写到这里,我们知道登陆页面,客户端就emit一个名字叫做online的方法,传的参数是user为cookie的登录用户。之后如果app.js中存在代码:

      socket.on('online', function (data) {
        socket.name = data.user
        if (!users[data.user]) {
          users[data.user] = data.user
        }
        io.sockets.emit('online', {users: users, user: data.user})
      })

    我们就监听到了服务端online方法了,之后运行逻辑在emit,之后客户端在监听,基本上就能实现所有功能了,具体设计自己实现。

    下面来展示自己的代码和效果(仅供参考)。

    node的socket.io:

    var users = {}
    var server = http.createServer(app)
    var io = require('socket.io').listen(server)
    io.sockets.on('connection', function (socket) {
      socket.on('online', function (data) {
        socket.name = data.user
        if (!users[data.user]) {
          users[data.user] = data.user
        }
        io.sockets.emit('online', {users: users, user: data.user})
      })
      socket.on('say', function (data) {
        chatApi.insertChat(data, function (cont) {
          if (cont) {
            if (data.to === 'all') {
              socket.broadcast.emit('say', data)
            } else {
              var clients = io.sockets.clients()
              clients.forEach(function (client) {
                if (client.name === data.to) {
                  client.emit('say', data)
                }
              })
            }
            chatApi.upDataChatList(data, function (conts) {
            })
          }
        })
      })
      socket.on('focus', function (data) {
        var clients = io.sockets.clients()
        clients.forEach(function (client) {
          if (client.name === data.to) {
            client.emit('focus', data)
          }
        })
      })
      socket.on('blur', function (data) {
        var clients = io.sockets.clients()
        clients.forEach(function (client) {
          if (client.name === data.to) {
            client.emit('blur', data)
          }
        })
      })
      socket.on('see', function (data) {
        chatApi.updateChat(data, function (conts) {
          console.log('conts--->', conts)
          var clients = io.sockets.clients()
          clients.forEach(function (client) {
            if (client.name === data.to) {
              client.emit('see', data)
            }
          })
        })
      })
      socket.on('disconnect', function () {
        if (users[socket.name]) {
          delete users[socket.name]
          socket.broadcast.emit('offline', {users: users, user: socket.name})
        }
      })
    })

    node连接mysql做的后台:

    var mysql = require('mysql')
    var connection = mysql.createConnection({
      host: 'localhost',
      database: 'shop',
      user: 'root',
      password: '123456'
    })
    function handleDisconnect (connection) {
      connection.on('error', function (err) {
        if (!err.fatal) {
          return
        }
        if (err.code !== 'PROTOCOL_CONNECTION_LOST') {
          throw err
        }
        console.log('Re-connecting lost connection: ' + err.stack)
        connection = mysql.createConnection(connection.config)
        handleDisconnect(connection)
        connection.connect()
      })
    }
    handleDisconnect(connection)
    connection.connect()
    
    module.exports = {
      insertChat: function (data, callback) {
        console.log([data.from, data.to, data.msg])
        connection.query('insert into chat(chat_id,chat.from,chat.to,msg,chat.read)values(null,?,?,?,0)', [data.from, data.to, data.msg], function (err, rows, fields) {
          callback(rows)
        })
      },
      upDataChatList: function (data, callback) {
        connection.query('select * from chatList where chatList_username = ? and chatList_chatname = ?', [data.to, data.from], function (err, rows, fields) {
          console.log('rows--->', rows)
          if (rows[0]) {
            console.log('info--->', [rows[0].chatList_chat + 1, data.msg, data.to, data.from])
            connection.query('UPDATE chatList SET chatList_chat = ? where chatList_username = ? and chatList_chatname = ? ', [rows[0].chatList_chat + 1, data.to, data.from], function (err, cont, fields) {
            })
            connection.query('UPDATE chatList SET chatList_content = ? where chatList_username = ? and chatList_chatname = ? ', [data.msg, data.to, data.from], function (err, cont, fields) {
            })
            connection.query('UPDATE chatList SET chatList_time = ? where chatList_username = ? and chatList_chatname = ? ', [new Date().getTime(), data.to, data.from], function (err, cont, fields) {
              callback(cont)
            })
          } else {
            connection.query('insert into chatList(chatList_id,chatList_username,chatList_chatname,chatList_time,chatList_content,chatList_chat)values(null,?,?,?,?,1)', [data.to, data.from, new Date().getTime(), data.msg], function (err, cont, fields) {
              callback(cont)
            })
          }
          callback(rows)
        })
      },
      getChatDate: function (req, res, callback) {
        connection.query('select * from chat where (chat.from = ? and chat.to = ?) or (chat.from = ? and chat.to = ?) order by chat_id desc limit 0,6', [req.body.from, req.body.to, req.body.to, req.body.from], function (err, rows, fields) {
          callback(rows)
        })
      },
      getHistoryDate: function (req, res, callback) {
        connection.query('select * from chat where (chat.from = ? and chat.to = ?) or (chat.from = ? and chat.to = ?) order by chat_id desc limit ?,?', [req.body.from, req.body.to, req.body.to, req.body.from, (req.body.index - 1) * 20 + 6, req.body.index * 20 + 6], function (err, rows, fields) {
          callback(rows)
        })
      },
      getChatListData: function (req, res, callback) {
        connection.query('select * from chatList where chatList_username = ? order by chatList_time desc', [req.body.username], function (err, rows, fields) {
          callback(rows)
        })
      },
      updateChatList: function (req, res, callback) {
        connection.query('UPDATE chatList SET chatList_chat = 0 where chatList_username = ? and chatList_chatname = ?', [req.body.username, req.body.chatname], function (err, rows, fields) {
          callback(rows)
        })
      },
      updateChat: function (data, callback) {
        connection.query('UPDATE chat SET chat.read = 1 where chat.from = ? and chat.to = ? and chat.read=0', [data.to, data.from], function (err, rows, fields) {
          callback(rows)
        })
      }
    }

    这里偷懒了- -,mysql的链接应该自己写一个config文件里面而不是这里面。

    jade模版(list页面):

    doctype html
    html(lang="zh-CN" ng-app="hyyApp" ng-controller="chat")
      head
        title(ng-bind='chatName')
        meta(name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")
      include ./includes/head
        link(href="/stylesheets/chatList.css" rel='stylesheet')
      body
        .row
          .col-lg-12
            .input-group
              input.form-control(type="text")
              span.input-group-btn
                button.btn.btn-default(type="button") serach
        .container
          ul
            li.chatListLi(ng-repeat='chatList in chatLists' ng-click='chatListClick(chatList)')
              img(src='/images/patient.png')
              .chatListInfo
                .chatListTop
                  .chatListTitle(ng-bind='chatList.chatList_chatname')
                  .chatList(ng-bind='chatList.time')
                  .badge(ng-bind='chatList.chatList_chat' ng-if='chatList.chatList_chat')
                .chatListContent(ng-bind='chatList.chatList_content')
        include ./includes/body
        script(src='javascripts/layout/zepto.js')
        script(src='javascripts/layout/touch.js')
        script(src='/javascripts/chatList.js')

    js(list页面):

      var TimeByClinic = function ($scope, $http) {
        var socket = window.io.connect()
        var from = window.$.cookie('user')
        $scope.chatLists = []
        $scope.timeStamp = new Date().getTime()
        function getTime (date) {
          for (var i = 0; i < date.length; i++) {
            date[i].year = new Date(parseInt(date[i].chatList_time)).getFullYear()
            date[i].month = new Date(parseInt(date[i].chatList_time)).getMonth() + 1
            date[i].data = new Date(parseInt(date[i].chatList_time)).getDate()
            if ($scope.timeStamp - date[i].chatList_time <= 86400000) {
              if (new Date(parseInt(date[i].chatList_time)).getMinutes() < 10) {
                date[i].time = new Date(parseInt(date[i].chatList_time)).getHours() + ':0' + new Date(parseInt(date[i].chatList_time)).getMinutes()
              } else {
                date[i].time = new Date(parseInt(date[i].chatList_time)).getHours() + ':' + new Date(parseInt(date[i].chatList_time)).getMinutes()
              }
            } else {
              date[i].time = date[i].data + '|' + date[i].month + '|' + date[i].year
            }
          }
          console.log(date)
        }
        function chatList () {
          $http({
            url: '/getChatListData',
            method: 'POST',
            data: {
              'username': window.utils.getQuery('username')
            }
          }).success(function (data) {
            $scope.chatLists = data
            getTime(data)
          })
        }
        function updateChatList (o) {
          $http({
            url: '/updateChatList',
            method: 'POST',
            data: {
              'username': window.utils.getQuery('username'),
              'chatname': o.chatList_chatname
            }
          }).success(function (data) {
            console.log(data)
          })
        }
        chatList()
        $scope.chatListClick = function (o) {
          updateChatList(o)
          var str = '/chat?' + 'username=' + o.chatList_username + '&chatName=' + o.chatList_chatname
          window.location = str
        }
        socket.emit('online', {user: from})
        socket.on('online', function (data) {
          console.log(data)
        })
        socket.on('say', function (data) {
          console.log(data)
          chatList()
          $scope.$apply()
        })
      }
      window.hyyApp.controller('chat', ['$scope', '$http', TimeByClinic])

    include进来公共body:

    script(src='/javascripts/layout/angular.min.js')
    script 
      |window.hyyApp = window.angular.module('hyyApp', [])
    script(src='/layout/until.js')
    script(src='javascripts/layout/jquery.cookie.js')
    script(src='/socket.io/socket.io.js')

    这里的前三行代码是自己为了实现angular模块发开,第四行是自己写的工具类,请忽略!!!

    数据库(mysql):

    效果图如下:

     大概是这样子一个人和另一个人说话,就会产生一条数据:

    safari的list表单如果要进入就会读取这条消息,同时chrome的chat页面最下面的消息就会变成已读

    效果如下:

    然后我们safari的页面在退回到list表单的时候我们就会发现消息提示消失,时间和内容更新

    效果如下:

    之后我们再来看chat页面,首先看一下代码:

    jade:

    doctype html
    html(lang="zh-CN" ng-app="hyyApp" ng-controller="chat")
      head
        title(ng-bind='chatName')
        meta(name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")
      include ./includes/head
        link(href="/stylesheets/chat.css" rel='stylesheet')
      body
        p.chatCenter.title 
          span.titleBack(ng-click='back()') &lt;&nbsp;back
          span.titleName(ng-bind='showName')
        .container#contentChat
          div(ng-repeat='data in chatDate')
            .chatContentLeft(ng-if='data.to === username')
              .chatLeftFlag1
              .chatLeftFlag2
              img(src='/images/patient.png')
              div
                p(ng-bind='data.msg')
            .chatContentRight(ng-if='data.from === username')
              .chatRightFlag1
              .chatRightFlag2
              img(src='/images/patient.png')
              div 
                p(ng-bind='data.msg' ng-click='until(data)')
                span.chatStatus(ng-if='data.read') 已读
              .clear
        #chatInput
          .input-group
            input.form-control(type="text" ng-model='input' ng-blur="blur()" ng-focus="focus()")
            span.input-group-btn
              botton.btn.btn-default(type="button" ng-click='say()' ng-if='input') submit
              botton.btn.btn-default(disabled type="button" ng-if='!input') submit
        include ./includes/body
        script(src='javascripts/layout/zepto.js')
        script(src='javascripts/layout/touch.js')
        script(src='javascripts/layout/jquery.cookie.js')
        script(src='/socket.io/socket.io.js')
        script(src='/javascripts/chat.js')

    js代码:

      var TimeByClinic = function ($scope, $http) {
        $scope.input = ''
        $scope.username = window.utils.getQuery('username')
        $scope.chatName = window.utils.getQuery('chatName')
        $scope.showName = window.utils.getQuery('chatName')
        $scope.height = window.$(document).height()
        $scope.chatDate = []
        $scope.index = 1
        $scope.flag = true
        $scope.touchStart = []
        $scope.touchMove = []
        var socket = window.io.connect()
        var from = window.$.cookie('user')
        /* update chatlist for msg state. */
        function updateChatList () {
          $http({
            url: '/updateChatList',
            method: 'POST',
            data: {
              'username': window.utils.getQuery('username'),
              'chatname': window.utils.getQuery('chatName')
            }
          }).success(function (data) {
            console.log(data)
          })
        }
        /* update chat for read state. */
        function updateChat () {
    
        }
        updateChat()
        /* GET showchat for six data chat msg. */
        function getDate () {
          $http({
            url: '/getChatDate',
            method: 'POST',
            data: {
              'from': window.utils.getQuery('username'),
              'to': window.utils.getQuery('chatName')
            }
          }).success(function (data) {
            console.log(data)
            $scope.chatDate = data.reverse()
          })
        }
        /* touch event. */
        function touchStart (event) {
          var touch = event.touches[0]
          $scope.touchStart = [touch.pageX, touch.pageY, new Date().getTime()]
        }
        function touchMove (event) {
          var touch = event.touches[0]
          $scope.touchMove = [touch.pageX, touch.pageY, new Date().getTime()]
        }
        function touchEnd (event) {
          if ($scope.touchMove[1] - $scope.touchStart[1] >= 200 && $scope.touchMove[2] - $scope.touchStart[2] <= 666) {
            if (window.$(document).scrollTop() <= 0) {
              historyData()
            }
          }
        }
        document.addEventListener('touchstart', touchStart, false)
        document.addEventListener('touchmove', touchMove, false)
        document.addEventListener('touchend', touchEnd, false)
        /* GET historyData. */
        function historyData () {
          if ($scope.flag) {
            $scope.flag = false
            $http({
              url: '/getHistoryDate',
              method: 'POST',
              data: {
                'from': window.utils.getQuery('username'),
                'to': window.utils.getQuery('chatName'),
                'index': $scope.index
              }
            }).success(function (data) {
              console.log(data)
              if (data[0]) {
                $scope.chatDate = data.reverse().concat($scope.chatDate)
                setTimeout(function () {
                  $scope.flag = true
                  $scope.index++
                }, 2000)
              } else {
                $scope.more = false
              }
              if (data.length < 20) {
                $scope.more = false
                $scope.flag = false
              }
            })
          }
        }
        /* UPDATE view data state. */
        function readState () {
          for (var i = 0; i < $scope.chatDate.length; i++) {
            if ($scope.chatDate[i].read === 0) {
              $scope.chatDate[i].read = 1
            }
          }
          $scope.$apply()
        }
        /* GET now time. */
        function nowTime () {
          var date = new Date()
          var time = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + (date.getMinutes() < 10 ? ('0' + date.getMinutes()) : date.getMinutes()) + ":" + (date.getSeconds() < 10 ? ('0' + date.getSeconds()) : date.getSeconds());
          return time
        }
        getDate()
        /* socket.io emit and on. */
        socket.emit('online', {user: from})
        socket.emit('see', {from: from, to: window.utils.getQuery('chatName')})
        socket.on('online', function (data) {
          console.log(data)
          console.log(data.from)
          var str = ''
          if (data.user !== from) {
            // str = '<p class="chatCenter">用户 ' + data.user + ' 上线了!</p>'
          } else {
            // str = '<p class="chatCenter">' + nowTime() + '</p>'
          }
          window.$('.container').append(str)
          window.$(document).scrollTop(window.$(document).height() - $scope.height)
        })
        socket.on('see', function (data) {
          readState()
        })
        socket.on('focus', function (data) {
          if (data.from === window.utils.getQuery('chatName')) {
            $scope.focusNumber = 0
            $scope.showName = '对方正在讲话'
            $scope.interval = setInterval(function () {
              if ($scope.focusNumber === 3) {
                $scope.showName = '对方正在讲话'
                $scope.focusNumber = 0
              } else {
                $scope.showName += '.'
                $scope.focusNumber++
              }
              $scope.$apply()
            }, 1000)
            $scope.$apply()
          }
        })
        socket.on('blur', function (data) {
          $scope.showName = window.utils.getQuery('chatName')
          clearInterval($scope.interval)
          $scope.$apply()
        })
        socket.on('say', function (data) {
          updateChatList()
          console.log(data)
          var obj = {
            'from': window.utils.getQuery('chatName'),
            'to': window.utils.getQuery('username'),
            'read': 0,
            'msg': data.msg
          }
          $scope.chatDate.push(obj)
          // var str = '<div class="chatContentLeft">' +
          //           '<div class="chatLeftFlag1"></div>' +
          //           '<div class="chatLeftFlag2"></div>' +
          //           '<img src="/images/patient.png"/>' +
          //           '<div>' +
          //             '<p>' + data.msg + '</p>' +
          //           '</div>' +
          //         '</div>'
          // window.$('.container').append(str)
          socket.emit('see', {from: from, to: window.utils.getQuery('chatName')})
          window.$(document).scrollTop(window.$(document).height() - $scope.height)
        })
        $scope.say = function () {
          var obj = {
            'from': window.utils.getQuery('username'),
            'to': window.utils.getQuery('chatName'),
            'read': 0,
            'msg': $scope.input
          }
          // var str = '<div class="chatContentRight">' +
          //             '<div class="chatRightFlag1"></div>' +
          //             '<div class="chatRightFlag2"></div>' +
          //             '<img src="/images/patient.png"/>' +
          //             '<div>' +
          //               '<p>' + $scope.input + '</p>' +
          //             '</div>' +
          //             '<div class="clear"></div>' +
          //           '</div>'
          // window.$('.container').append(str)
          $scope.chatDate.push(obj)
          window.$(document).scrollTop(window.$(document).height() - $scope.height)
          socket.emit('say', {from: from, to: window.utils.getQuery('chatName'), msg: $scope.input})
          $scope.input = ''
        }
        $scope.until = function (o) {
          console.log(o)
        }
        $scope.blur = function () {
          socket.emit('blur', {from: from, to: window.utils.getQuery('chatName')})
        }
        $scope.focus = function () {
          console.log(2)
          socket.emit('focus', {from: from, to: window.utils.getQuery('chatName')})
        }
        $scope.back = function () {
          var str = '/chatList?username=' + window.utils.getQuery('username')
          window.location = str
        }
      }
      window.hyyApp.controller('chat', ['$scope', '$http', TimeByClinic])

    数据库:

    数据有点多就截取了一部分。

    之后我们就实现了两个页面的通信,当然页面和页面的通信消息会直接变成已读。然后我们上下滑动就能加载出我们的历史记录,这些逻辑都写在了js里面了。

    效果如下:

    另外我还模拟了微信的对方正在说话中

    效果如下:

    一方获取焦点另一方就会变成对方正在讲话的样式,然后等焦点blur的时候在变成正常的名字

    具体的逻辑就随便你加了,是不是很简单呢?

    之前和boss直聘leader聊到这的web及时通信,貌似他们用的是mqtt框架实现的,而不同公司用的肯定也是不一样的,我同学用的融云,而我们公司用的确实环信,不敢谈到这些东西的利弊,因为自己根本没有大规模的这种web即时通信的开发经验,所以还是处于井底之蛙的阶段,不敢妄加评论。但是这些开发的东西其实都是大同小异的,最起码开发文档都是类似的,学习成本也比node的这个原声socket.io要容易的多!

    过段时间我会更新socket.io的blog(前提是有时间去继续开发- -),实现群组聊天,表情,消息回撤,消息加密等等更加高级的socket技术!有兴趣的可以继续期待。。。欢迎有不同意见的人前来讨论,希望我的blog对您有帮助!

  • 相关阅读:
    BigDecimal
    android sdk manager 无法更新,解决连不上dl.google.com的问题
    程序卡在 while(SPI_I2S_GetFlagStatus(W5500_SPI, SPI_I2S_FLAG_TXE) == RESET) 处
    获取本设备IP地址
    Xamarin Android 监听音量键(下)
    xamarin Android 监听音量键(上)
    最大子序列和
    2.找出单独出现的数字
    编程题常见输入格式处理方法
    二进制比较:破解 LuaJIT 加密脚本的一种新思路。直接修改,无需反编译
  • 原文地址:https://www.cnblogs.com/jcscript/p/5976641.html
Copyright © 2011-2022 走看看