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對您有幫助!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM