前一段事件,我一个同窗给他们公司用融云搭建了一套web及时通讯系统,而后以前个人公司也用过环云来实现web及时通讯,本人对web及时通讯仍是很是感兴趣的。私下读了融云和环信的开发文档,而后发现若是注册第三方貌似开发群聊就是个问题了(由于本身作个两我的聊天很简单,想作的在复杂些就要花钱了- -orz),包括后期我想开发表情包,群聊,和消息加密等等。本着节省的原则尝试去看了一下socket.io,结果一发不可收拾。。。好了闲话少说,今天来分享一份关于socket.io的东西!!javascript
首先咱们搭建了一个express的项目,我直接用了express的默认jade,小伙伴们喜欢ejs就本身建立成ejs模版的项目!php
以后咱们引进socket,代码以下:css
{ "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自动加载包依赖。html
咱们加载了socket.io以后咱们进入到app.js来创建socket.io的connect,代码以下java
var users = {} var server = http.createServer(app) var io = require('socket.io').listen(server)
咱们的users用来存全部的在线用户,第二行的app是express框架自动生成。以后咱们的io就是socket的实力,咱们能够调用io下的socket.on方法node
代码以下:mysql
io.sockets.on('connection', function (socket) { }
这样每当咱们在iterm上启动服务器那么socket.io就会自动链接,而后咱们方法里面有一个回调函数,socket这个参数咱们就能够去on本身想要的事件了,这里的事件是自定义名称,你能够叫a,也能够叫b,可是最好语意话一些!jquery
咱们来讲一下咱们用的几个基本方法,由于socket.io是消息推送,而不像ajax长轮询那样子。因此咱们确定是须要一个方法去坚挺客户端发送的方法,这里不论是服务端仍是客户端其实都是一个原理,而后咱们还须要一个方法去推送数据!web
监听方法代码以下: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()') < 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对您有帮助!