node.js和socket.io实现im
im——Instant Messaging 即时通讯
基本技术原理
(1)通过IM服务器登陆或注销
(2)用户A通过列表找到B,用户B获得消息并与之交谈
(3)通过IM服务器指引建立与B单独的通讯通道
通讯方式
(1)在线直接通讯
直接通过服务器发送过来的用户B的IP地址、TCP端口号等信息,直接向用户B的PC机发出聊天信息,即时文字消息就不再IM服务器中转,直接通过网络进行点对点的通讯
(2)在线代理通讯
点对点通讯由于防火墙、网络速度等原因难以建立或者速度很慢,IM服务器将会主动提供消息中转服务
(3)离线代理通讯
不能同时在线的时候,如此时A向B发送消息,IM服务器可以主动寄存A用户的消息,到B用户下一次登陆的时候,自动将消息转发 给B
(4)扩展方式通讯
用户A可以通过IM服务器将信息以扩展的方式传递给B,如短信、email等
用nodejs实现im使用到express和socket.io两个包模块
expres
是node.js中管理路由响应请求的模块,根据请求的URL返回相应的HTML页面
安装
npm install express
不用express需要将HTML代码与后台JavaScript代码写在一起进行请求的响应
var http = require('http'); server = http.createServer(function(req,res){ res.writeHead(200,{ 'Content-Type':'text/html' }); res.write('<h1>hello world!</h1><div style="color:blue">baby</div>'); res.end(); }); server.listen(8080); console.log('server started');
使用express,在当前目录下建www用来存放我们的网页文件,包括图片以及前端的js文件等
server.js修改为
var express = require('express'), app = express(), server = require('http').createServer(app); app.use('/',express.static(__dirname+'/www/')); server.listen(8080); console.log('server started');
www下见文件index.html
<!doctype html> <html> <head> <title>mychat</title> </head> <body> <div class="wrapper"> <div class="banner"> <h1>My Chat</h1> <span id="status"></span> </div> <div id="historyMsg"> </div> <div class="controls" > <textarea id="messageInput" placeHolder="enter to send"></textarea> <input id="sendBtn" type="button" value="SEND"> </div> </div> </body> </html>
重新运行server.js
socket.io
Node.js中使用socket的一个包。使用它可以很方便地建立服务器到客户端的sockets连接,发送事件与接收特定事件
安装
npm install socket.io
安装后在node_modules文件夹下新生成了一个socket.io文件夹,其中可以找到一个socket.io.js文件
将它引入到HTML页面,这样我们就可以在前端使用socket.io与服务器进行通信了
通过socket.emit()来激发一个事件
通过socket.on()来侦听和处理对应事件
这两个事件通过传递的参数进行通信
查看安装的模块
eg:
index.html
<!doctype html> <html> <head> <title>mychat</title> </head> <body> <div class="wrapper"> <div class="banner"> <h1>My Chat</h1> <span id="status"></span> </div> <div id="historyMsg"> </div> <div class="controls" > <textarea id="messageInput" placeHolder="enter to send"></textarea> <input id="sendBtn" type="button" value="SEND"> </div> </div> <script src="/socket.io/socket.io.js"></script> <script src="js/jquery.js"></script> <script type="text/javascript"> var socket=io.connect(),//与服务器进行连接 button=document.getElementById('sendBtn'); button.onclick=function(){ var msg= $("#messageInput").val(); socket.emit('foo', msg);//发送一个名为foo的事件,并且传递一个字符串数据‘hello’ } </script> </body> </html>
首先建立与服务器的连接,然后得到一个socket实例
之后如果页面上面一个ID为sendBtn的按钮被点击的话,通过这个socket实例发起一个名为foo的事件,同时传递一个hello字符串信息到服务器
在服务器端写相应的代码来处理这个foo事件并接收传递来的数据
var express = require('express'), app = express(), server = require('http').createServer(app), io = require('socket.io').listen(server); app.use('/',express.static(__dirname+'/www/')); server.listen(8080); io.on('connection',function(socket){ socket.on('foo',function(data){ console.log(data); }) }); console.log('server started');
1.设置昵称
在后台server.js中,创建一个名叫users的全局数组变量,当一个用户设置好昵称发送到服务器的时候,将昵称压入users数组。如果用户断线离开了,也要相应地从users数组中移除
index.html
<!doctype html> <html> <head> <title>mychat</title> </head> <body> <div class="wrapper"> <div class="banner"> <h1>My Chat</h1> <span id="status"></span> </div> <div id="historyMsg"> </div> <div class="controls" > <textarea id="messageInput" placeHolder="enter to send"></textarea> <input id="sendBtn" type="button" value="SEND"> </div> </div> <div id="loginWrapper"> <p id="info">connecting to server...</p> <div id="nickWrapper" style="display:none;"> <input type="text" placeHolder="nickname" id="nicknameInput" /> <input type="button" value="OK" id="loginBtn" /> </div> </div> <script src="/socket.io/socket.io.js"></script> <script src="js/jquery.js"></script> <script src="js/chat.js"></script> </body> </html>
chat.js
var chat; $(function(){ chat = new Chat(); chat.init(); $("#loginWrapper").show(); $(".wrapper").hide(); $("#loginBtn").click(function(){ var nickName = $('#nicknameInput').val(); if (nickName.trim().length != 0) { chat.socket.emit('login', nickName); } else { $('#nicknameInput').focus(); }; }); }); //定义chat类 var Chat = function() { this.socket = null; }; //向原型添加业务方法 Chat.prototype = { init: function() {//初始化 var that = this; //建立到服务器的socket连接 this.socket = io.connect(); //监听socket的connect事件,此事件表示连接已经建立 this.socket.on('connect', function() { //连接到服务器后,显示昵称输入框 $('#info').html('get yourself a nickname'); $('#nickWrapper').show(); $('#nicknameInput').focus(); }); this.socket.on('nickExisted', function() { $('#info').html('nickname is taken, choose another please'); //显示昵称被占用的提示 }); this.socket.on('loginSuccess', function() { $(document).attr("title",'Chat | ' + $('#nicknameInput').val()); $('#loginWrapper').hide();//隐藏遮罩层显聊天界面 $(".wrapper").show(); $('#messageInput').focus();//让消息输入框获得焦点 }); } };
server.js
var express = require('express'), app = express(), server = require('http').createServer(app), io = require('socket.io').listen(server), users =[]; app.use('/',express.static(__dirname+'/www/')); server.listen(8080); io.on('connection',function(socket){ socket.on('login',function(nickname){ if(users.indexOf(nickname)>-1){ socket.emit('nickExisted'); }else{ socket.userIndex = users.length; socket.nickname = nickname; users.push(nickname); socket.emit('loginSuccess'); io.sockets.emit('system', nickname, users.length, 'login'); } }); }); console.log('server started');
2.在线统计
通过io.sockets.emit 向所有用户发送了一个system事件,传递了刚登入或离开用户的昵称
chat.js
var chat; $(function(){ chat = new Chat(); chat.init(); $("#loginWrapper").show(); $(".wrapper").hide(); $("#loginBtn").click(function(){ var nickName = $('#nicknameInput').val(); if (nickName.trim().length != 0) { chat.socket.emit('login', nickName); } else { $('#nicknameInput').focus(); }; }); }); //定义chat类 var Chat = function() { this.socket = null; }; //向原型添加业务方法 Chat.prototype = { init: function() {//初始化 var that = this; //建立到服务器的socket连接 this.socket = io.connect(); //监听socket的connect事件,此事件表示连接已经建立 this.socket.on('connect', function() { //连接到服务器后,显示昵称输入框 $('#info').html('get yourself a nickname'); $('#nickWrapper').show(); $('#nicknameInput').focus(); }); this.socket.on('nickExisted', function() { $('#info').html('nickname is taken, choose another please'); //显示昵称被占用的提示 }); this.socket.on('loginSuccess', function() { $(document).attr("title",'Chat | ' + $('#nicknameInput').val()); $('#loginWrapper').hide();//隐藏遮罩层显聊天界面 $(".wrapper").show(); $('#messageInput').focus();//让消息输入框获得焦点 }); this.socket.on('system', function(nickName, userCount, type) { //判断用户是连接还是离开以显示不同的信息 var msg = nickName + (type == 'login' ? ' join in' : 'left'); $('#historyMsg').append("<p>"+msg+"</p>"); chat._displayNewMsg('system ', msg, 'red'); //将在线人数显示到页面顶部 $('#status').html(userCount + (userCount > 1 ? ' users' : ' user') + ' online'); }); } };
server.js
var express = require('express'), app = express(), server = require('http').createServer(app), io = require('socket.io').listen(server), users =[]; app.use('/',express.static(__dirname+'/www/')); server.listen(8080); io.on('connection',function(socket){ socket.on('login',function(nickname){ if(users.indexOf(nickname)>-1){ socket.emit('nickExisted'); }else{ socket.userIndex = users.length; socket.nickname = nickname; users.push(nickname); socket.emit('loginSuccess'); io.sockets.emit('system', nickname, users.length, 'login'); } }); socket.on('disconnect', function() { users.splice(socket.userIndex, 1); socket.broadcast.emit('system', socket.nickname, users.length, 'logout'); }); }); console.log('server started');
用户登录和离开显示提示
3.发送消息
chat.js
var chat; $(function(){ chat = new Chat(); chat.init(); $("#loginWrapper").show(); $(".wrapper").hide(); $("#loginBtn").click(function(){ var nickName = $('#nicknameInput').val(); if (nickName.trim().length != 0) { chat.socket.emit('login', nickName); } else { $('#nicknameInput').focus(); }; }); $('#sendBtn').click(function() { var messageInput = $('#messageInput'), msg = messageInput.val(); messageInput.val(''); messageInput.focus(); if (msg.trim().length != 0) { chat.socket.emit('postMsg', msg); //把消息发送到服务器 chat._displayNewMsg('me', msg); //把自己的消息显示到自己的窗口中 }; }); }); //定义chat类 var Chat = function() { this.socket = null; }; //向原型添加业务方法 Chat.prototype = { init: function() {//初始化 var that = this; //建立到服务器的socket连接 this.socket = io.connect(); //监听socket的connect事件,此事件表示连接已经建立 this.socket.on('connect', function() { //连接到服务器后,显示昵称输入框 $('#info').html('get yourself a nickname'); $('#nickWrapper').show(); $('#nicknameInput').focus(); }); this.socket.on('nickExisted', function() { $('#info').html('nickname is taken, choose another please'); //显示昵称被占用的提示 }); this.socket.on('loginSuccess', function() { $(document).attr("title",'Chat | ' + $('#nicknameInput').val()); $('#loginWrapper').hide();//隐藏遮罩层显聊天界面 $(".wrapper").show(); $('#messageInput').focus();//让消息输入框获得焦点 }); this.socket.on('system', function(nickName, userCount, type) { //判断用户是连接还是离开以显示不同的信息 var msg = nickName + (type == 'login' ? ' join in' : 'left'); $('#historyMsg').append("<p>"+msg+"</p>"); chat._displayNewMsg('system ', msg, 'red'); //将在线人数显示到页面顶部 $('#status').html(userCount + (userCount > 1 ? ' users' : ' user') + ' online'); }); this.socket.on('newMsg', function(user, msg) { chat._displayNewMsg(user, msg); }); }, _displayNewMsg: function(user, msg, color) {//显示消息 var container = $('#historyMsg'), msgToDisplay = '', date = new Date().toTimeString().substr(0, 8), default_color = color || '#000'; msgToDisplay = '<p>'+user + '<span class="timespan">(' + date + '): </span>' + msg+'</p>'; container.append(msgToDisplay); container.scrollTop = container.scrollHeight; } };
server.js
var express = require('express'), app = express(), server = require('http').createServer(app), io = require('socket.io').listen(server), users =[]; app.use('/',express.static(__dirname+'/www/')); server.listen(8080); io.on('connection',function(socket){ socket.on('login',function(nickname){ if(users.indexOf(nickname)>-1){ socket.emit('nickExisted'); }else{ socket.userIndex = users.length; socket.nickname = nickname; users.push(nickname); socket.emit('loginSuccess'); io.sockets.emit('system', nickname, users.length, 'login'); } }); socket.on('disconnect', function() { users.splice(socket.userIndex, 1); socket.broadcast.emit('system', socket.nickname, users.length, 'logout'); }); socket.on('postMsg', function(msg) { //将消息发送到除自己外的所有用户 socket.broadcast.emit('newMsg', socket.nickname, msg); }); }); console.log('server started');
4.发送图片
图片不同于文字,但通过将图片转化为字符串形式后,便可以像发送普通文本消息一样发送图片了,只是在显示的时候将它还原为图片
文件类型的input,用户点击图片按钮后,弹出文件选择窗口选择图片。然后在JavaScript代码中使用FileReader来将图片读取为base64格式的字符串形式进行发送
而base64格式的图片直接可以指定为图片的src,这样就可以将图片用img标签显示在页面了
index.html
<!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="author" content="Wayou"> <meta name="description" content="hichat | a simple chat application built with node.js and websocket"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>hichat</title> <link rel="stylesheet" href="styles/main.css"> <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> <link rel="icon" href="favicon.ico" type="image/x-icon"> </head> <body> <div class="wrapper"> <div class="banner"> <h1>HiChat :)</h1> <span id="status"></span> </div> <div id="historyMsg"> </div> <div class="controls" > <div class="items"> <input id="colorStyle" type="color" placeHolder='#000' title="font color" /> <input id="emoji" type="button" value="emoji" title="emoji" /> <label for="sendImage" class="imageLable"> <input type="button" value="image" /> <input id="sendImage" type="file" value="image"/> </label> <input id="clearBtn" type="button" value="clear" title="clear screen" /> </div> <textarea id="messageInput" placeHolder="enter to send"></textarea> <input id="sendBtn" type="button" value="SEND"> <div id="emojiWrapper"> </div> </div> </div> <div id="loginWrapper"> <p id="info">connecting to server...</p> <div id="nickWrapper"> <input type="text" placeHolder="nickname" id="nicknameInput" /> <input type="button" value="OK" id="loginBtn" /> </div> </div> <footer> <small>view on <a href="https://github.com/Wayou/HiChat">GitHub</a> | <a href="mailto:liuwayong@gmail.com">contact me</a></small> </footer> <script src="/socket.io/socket.io.js"></script> <script src="scripts/hichat.js"></script> <script> /**REMOVE ME IF YOU CANT ACCESS GOOGLE SERVICE**/ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-46794744-7', 'hichat.herokuapp.com'); ga('send', 'pageview'); /**REMOVE END**/ </script> </body> </html>
chat.js
var chat; $(function(){ chat = new Chat(); chat.init(); $("#loginWrapper").show(); $(".wrapper").hide(); $("#loginBtn").click(function(){ var nickName = $('#nicknameInput').val(); if (nickName.trim().length != 0) { chat.socket.emit('login', nickName); } else { $('#nicknameInput').focus(); }; }); $('#sendBtn').click(function() { var messageInput = $('#messageInput'), msg = messageInput.val(); messageInput.val(''); messageInput.focus(); if (msg.trim().length != 0) { chat.socket.emit('postMsg', msg); //把消息发送到服务器 chat._displayNewMsg('me', msg); //把自己的消息显示到自己的窗口中 }; }); $('#sendImage').change(function() { //检查是否有文件被选中 if (this.files.length != 0) { //获取文件并用FileReader进行读取 var file = this.files[0], reader = new FileReader(); if (!reader) { chat._displayNewMsg('system', '!your browser doesn\'t support fileReader', 'red'); this.value = ''; return; }; reader.onload = function(e) { //读取成功,显示到页面并发送到服务器 this.value = ''; chat.socket.emit('img', e.target.result); chat._displayImage('me', e.target.result); }; reader.readAsDataURL(file); } }); }); //定义chat类 var Chat = function() { this.socket = null; }; //向原型添加业务方法 Chat.prototype = { init: function() {//初始化 var that = this; //建立到服务器的socket连接 this.socket = io.connect(); //监听socket的connect事件,此事件表示连接已经建立 this.socket.on('connect', function() { //连接到服务器后,显示昵称输入框 $('#info').html('get yourself a nickname'); $('#nickWrapper').show(); $('#nicknameInput').focus(); }); this.socket.on('nickExisted', function() { $('#info').html('nickname is taken, choose another please'); //显示昵称被占用的提示 }); this.socket.on('loginSuccess', function() { $(document).attr("title",'Chat | ' + $('#nicknameInput').val()); $('#loginWrapper').hide();//隐藏遮罩层显聊天界面 $(".wrapper").show(); $('#messageInput').focus();//让消息输入框获得焦点 }); this.socket.on('system', function(nickName, userCount, type) { //判断用户是连接还是离开以显示不同的信息 var msg = nickName + (type == 'login' ? ' join in' : 'left'); $('#historyMsg').append("<p>"+msg+"</p>"); chat._displayNewMsg('system ', msg, 'red'); //将在线人数显示到页面顶部 $('#status').html(userCount + (userCount > 1 ? ' users' : ' user') + ' online'); }); this.socket.on('newMsg', function(user, msg) { chat._displayNewMsg(user, msg); }); this.socket.on('newImg', function(user, img) { chat._displayImage(user, img); }); }, _displayNewMsg: function(user, msg, color) {//显示消息 var container = $('#historyMsg'), msgToDisplay = '', date = new Date().toTimeString().substr(0, 8), default_color = color || '#000'; msgToDisplay = '<p style="color:'+color+'">'+user + '<span class="timespan">(' + date + '): </span>' + msg+'</p>'; container.append(msgToDisplay); container.scrollTop = container.scrollHeight; }, _displayImage: function(user, imgData, color) { var container = $('#historyMsg'), msgToDisplay = '', date = new Date().toTimeString().substr(0, 8), mycolor = color || '#000'; msgToDisplay = '<p style="color:'+color+'">'+ user + '<span class="timespan">(' + date + '): </span> <br/>' + '<a href="' + imgData + '" target="_blank"><img src="' + imgData + '"/></a></p>'; container.append(msgToDisplay); container.scrollTop = container.scrollHeight; } };
server.js
var express = require('express'), app = express(), server = require('http').createServer(app), io = require('socket.io').listen(server), users =[]; app.use('/',express.static(__dirname+'/www/')); server.listen(8080); io.on('connection',function(socket){ socket.on('login',function(nickname){ if(users.indexOf(nickname)>-1){ socket.emit('nickExisted'); }else{ socket.userIndex = users.length; socket.nickname = nickname; users.push(nickname); socket.emit('loginSuccess'); io.sockets.emit('system', nickname, users.length, 'login'); } }); socket.on('disconnect', function() { users.splice(socket.userIndex, 1); socket.broadcast.emit('system', socket.nickname, users.length, 'logout'); }); socket.on('postMsg', function(msg) { //将消息发送到除自己外的所有用户 socket.broadcast.emit('newMsg', socket.nickname, msg); }); //接收用户发来的图片 socket.on('img', function(imgData) { //通过一个newImg事件分发到除自己外的每个用户 socket.broadcast.emit('newImg', socket.nickname, imgData); }); }); console.log('server started');
5.发表情
聊天程序是把表情转为符号,然后数据传输过程中其实转输的是一个冒号加右括号的组合,当每个客户端接收到消息后,从文字当中将这些表情符号提取出来,再用gif图片替换,这样呈现到页面我们就 看到了表情加文字的混排了
表情的格式[emoji:xx]
xx表示某个gif图片的编号
index.html
<!doctype html> <html> <head> <title>mychat</title> <style> /*custom the file input*/ .imageLable { position: relative; } #sendImage { position: absolute; width: 52px; left: 0; opacity: 0; overflow: hidden; } #historyMsg img { max-width: 99%; } #emojiWrapper { display: none; width: 500px; bottom: 105px; position: absolute; background-color: #aaa; box-shadow: 0 0 10px #555; } #emojiWrapper img { margin: 2px; padding: 2px; width: 25px; height: 25px; } #emojiWrapper img:hover { background-color: blue; } .emoji{ display: inline; } /*end custom file input*/ </style> </head> <body> <div class="wrapper"> <div class="banner"> <h1>My Chat</h1> <span id="status"></span> </div> <div id="historyMsg"> </div> <div class="controls" > <div class="items"> <input id="colorStyle" type="color" placeHolder='#000' title="font color" /> <input id="emoji" type="button" value="emoji" title="emoji" /> <label for="sendImage" class="imageLable"> <input type="button" value="image" /> <input id="sendImage" type="file" value="image"/> </label> <input id="clearBtn" type="button" value="clear" title="clear screen" /> </div> <textarea id="messageInput" placeHolder="enter to send"></textarea> <input id="sendBtn" type="button" value="SEND"> <div id="emojiWrapper"> </div> </div> </div> <div id="loginWrapper"> <p id="info">connecting to server...</p> <div id="nickWrapper" style="display:none;"> <input type="text" placeHolder="nickname" id="nicknameInput" /> <input type="button" value="OK" id="loginBtn" /> </div> </div> <script src="/socket.io/socket.io.js"></script> <script src="js/jquery.js"></script> <script src="js/chat.js"></script> </body> </html>
chat.js
var chat; $(function(){ chat = new Chat(); chat.init(); $("#loginWrapper").show(); $(".wrapper").hide(); $("#loginBtn").click(function(){ var nickName = $('#nicknameInput').val(); if (nickName.trim().length != 0) { chat.socket.emit('login', nickName); } else { $('#nicknameInput').focus(); }; }); $('#sendBtn').click(function() { var messageInput = $('#messageInput'), msg = messageInput.val(); messageInput.val(''); messageInput.focus(); if (msg.trim().length != 0) { chat.socket.emit('postMsg', msg); //把消息发送到服务器 chat._displayNewMsg('me', msg); //把自己的消息显示到自己的窗口中 }; }); $('#sendImage').change(function() { //检查是否有文件被选中 if (this.files.length != 0) { //获取文件并用FileReader进行读取 var file = this.files[0], reader = new FileReader(); if (!reader) { chat._displayNewMsg('system', '!your browser doesn\'t support fileReader', 'red'); this.value = ''; return; }; reader.onload = function(e) { //读取成功,显示到页面并发送到服务器 this.value = ''; chat.socket.emit('img', e.target.result); chat._displayImage('me', e.target.result); }; reader.readAsDataURL(file); } }); $('#emoji').click(function(e) { var emojiwrapper = $('#emojiWrapper'); emojiwrapper.show(); e.stopPropagation(); }); $("body").click(function(e) { var emojiwrapper = $('#emojiWrapper'); if (e.target != emojiwrapper) { emojiwrapper.hide(); }; }); $('#emojiWrapper').click(function(e) { //获取被点击的表情 var target = e.target; if (target.nodeName.toLowerCase() == 'img') { var messageInput = $('#messageInput'); messageInput.focus(); messageInput.val(messageInput.val() + '[emoji:' + target.title + ']'); }; }); }); //定义chat类 var Chat = function() { this.socket = null; }; //向原型添加业务方法 Chat.prototype = { init: function() {//初始化 var that = this; //建立到服务器的socket连接 this.socket = io.connect(); //监听socket的connect事件,此事件表示连接已经建立 this.socket.on('connect', function() { //连接到服务器后,显示昵称输入框 $('#info').html('get yourself a nickname'); $('#nickWrapper').show(); $('#nicknameInput').focus(); }); this.socket.on('nickExisted', function() { $('#info').html('nickname is taken, choose another please'); //显示昵称被占用的提示 }); this.socket.on('loginSuccess', function() { $(document).attr("title",'Chat | ' + $('#nicknameInput').val()); $('#loginWrapper').hide();//隐藏遮罩层显聊天界面 $(".wrapper").show(); $('#messageInput').focus();//让消息输入框获得焦点 }); this.socket.on('system', function(nickName, userCount, type) { //判断用户是连接还是离开以显示不同的信息 var msg = nickName + (type == 'login' ? ' join in' : 'left'); $('#historyMsg').append("<p>"+msg+"</p>"); chat._displayNewMsg('system ', msg, 'red'); //将在线人数显示到页面顶部 $('#status').html(userCount + (userCount > 1 ? ' users' : ' user') + ' online'); }); this.socket.on('newMsg', function(user, msg) { chat._displayNewMsg(user, msg); }); this.socket.on('newImg', function(user, img) { chat._displayImage(user, img); }); this._initialEmoji(); }, _displayNewMsg: function(user, msg, color) {//显示消息 var container = $('#historyMsg'), msgToDisplay = '', date = new Date().toTimeString().substr(0, 8), default_color = color || '#000'; msg = this._showEmoji(msg); msgToDisplay = '<p style="color:'+color+'">'+user + '<span class="timespan">(' + date + '): </span>' + msg+'</p>'; container.append(msgToDisplay); container.scrollTop = container.scrollHeight; }, _displayImage: function(user, imgData, color) { var container = $('#historyMsg'), msgToDisplay = '', date = new Date().toTimeString().substr(0, 8), mycolor = color || '#000'; msgToDisplay = '<p style="color:'+color+'">'+ user + '<span class="timespan">(' + date + '): </span> <br/>' + '<a href="' + imgData + '" target="_blank"><img src="' + imgData + '"/></a></p>'; container.append(msgToDisplay); container.scrollTop = container.scrollHeight; }, _initialEmoji: function() { var emojiContainer = $('#emojiWrapper'), docFragment = ''; for (var i = 69; i > 0; i--) { var emojiItem = '<img '; emojiItem += ' src="content/emoji/' + i + '.gif" '; emojiItem += 'title="'+i+'" >'; docFragment+=emojiItem; }; emojiContainer.append(docFragment); }, _showEmoji: function(msg) { var match, result = msg, reg = /\[emoji:\d+\]/g, emojiIndex, totalEmojiNum = $('#emojiWrapper').children().length; while (match = reg.exec(msg)) { emojiIndex = match[0].slice(7, -1); if (emojiIndex > totalEmojiNum) { result = result.replace(match[0], '[X]'); } else { result = result.replace(match[0], '<img class="emoji" src="content/emoji/' + emojiIndex + '.gif" />'); }; }; return result; } };
server.js
var express = require('express'), app = express(), server = require('http').createServer(app), io = require('socket.io').listen(server), users =[]; app.use('/',express.static(__dirname+'/www/')); server.listen(8080); io.on('connection',function(socket){ socket.on('login',function(nickname){ if(users.indexOf(nickname)>-1){ socket.emit('nickExisted'); }else{ socket.userIndex = users.length; socket.nickname = nickname; users.push(nickname); socket.emit('loginSuccess'); io.sockets.emit('system', nickname, users.length, 'login'); } }); socket.on('disconnect', function() { users.splice(socket.userIndex, 1); socket.broadcast.emit('system', socket.nickname, users.length, 'logout'); }); socket.on('postMsg', function(msg) { //将消息发送到除自己外的所有用户 socket.broadcast.emit('newMsg', socket.nickname, msg); console.log('server started'+msg); }); //接收用户发来的图片 socket.on('img', function(imgData) { //通过一个newImg事件分发到除自己外的每个用户 socket.broadcast.emit('newImg', socket.nickname, imgData); }); }); console.log('server started');
6.文字颜色
HTML5新增了一个专门用于颜色选取的input标签
每次发送消息到服务器的时候,多加一个color参数就可以了,同时,在显示消息时调用_displayNewMsg的时候将这个color传递过去
增加输入昵称和发送消息的回车事件
index.html不变
chat.js
var chat; $(function(){ chat = new Chat(); chat.init(); $("#loginWrapper").show(); $(".wrapper").hide(); $("#loginBtn").click(function(){ var nickName = $('#nicknameInput').val(); if (nickName.trim().length != 0) { chat.socket.emit('login', nickName); } else { $('#nicknameInput').focus(); }; }); $('#sendBtn').click(function() { var messageInput = $('#messageInput'), msg = messageInput.val(), color = $('#colorStyle').val(); messageInput.val(''); messageInput.focus(); if (msg.trim().length != 0) { chat.socket.emit('postMsg', msg,color); //把消息发送到服务器 chat._displayNewMsg('me', msg,color); //把自己的消息显示到自己的窗口中 }; }); $('#sendImage').change(function() { //检查是否有文件被选中 if (this.files.length != 0) { //获取文件并用FileReader进行读取 var file = this.files[0], reader = new FileReader(); if (!reader) { chat._displayNewMsg('system', '!your browser doesn\'t support fileReader', 'red'); this.value = ''; return; }; reader.onload = function(e) { //读取成功,显示到页面并发送到服务器 this.value = ''; chat.socket.emit('img', e.target.result); chat._displayImage('me', e.target.result); }; reader.readAsDataURL(file); } }); $('#emoji').click(function(e) { var emojiwrapper = $('#emojiWrapper'); emojiwrapper.show(); e.stopPropagation(); }); $("body").click(function(e) { var emojiwrapper = $('#emojiWrapper'); if (e.target != emojiwrapper) { emojiwrapper.hide(); }; }); $('#emojiWrapper').click(function(e) { //获取被点击的表情 var target = e.target; if (target.nodeName.toLowerCase() == 'img') { var messageInput = $('#messageInput'); messageInput.focus(); messageInput.val(messageInput.val() + '[emoji:' + target.title + ']'); }; }); $('#nicknameInput').keydown(function(e) { if (e.keyCode == 13) { var nickName = $('#nicknameInput').val(); if (nickName.trim().length != 0) { chat.socket.emit('login', nickName); } }; }); $('#messageInput').keydown(function(e) { var messageInput = $('#messageInput'), msg = messageInput.val(), color = $('#colorStyle').val(); if (e.keyCode == 13 && msg.trim().length != 0) { messageInput.val(''); chat.socket.emit('postMsg', msg, color); chat._displayNewMsg('me', msg, color); } }); }); //定义chat类 var Chat = function() { this.socket = null; }; //向原型添加业务方法 Chat.prototype = { init: function() {//初始化 var that = this; //建立到服务器的socket连接 this.socket = io.connect(); //监听socket的connect事件,此事件表示连接已经建立 this.socket.on('connect', function() { //连接到服务器后,显示昵称输入框 $('#info').html('get yourself a nickname'); $('#nickWrapper').show(); $('#nicknameInput').focus(); }); this.socket.on('nickExisted', function() { $('#info').html('nickname is taken, choose another please'); //显示昵称被占用的提示 }); this.socket.on('loginSuccess', function() { $(document).attr("title",'Chat | ' + $('#nicknameInput').val()); $('#loginWrapper').hide();//隐藏遮罩层显聊天界面 $(".wrapper").show(); $('#messageInput').focus();//让消息输入框获得焦点 }); this.socket.on('system', function(nickName, userCount, type) { //判断用户是连接还是离开以显示不同的信息 var msg = nickName + (type == 'login' ? ' join in' : 'left'); $('#historyMsg').append("<p>"+msg+"</p>"); chat._displayNewMsg('system ', msg, 'red'); //将在线人数显示到页面顶部 $('#status').html(userCount + (userCount > 1 ? ' users' : ' user') + ' online'); }); this.socket.on('newMsg', function(user, msg,color) { chat._displayNewMsg(user, msg,color); }); this.socket.on('newImg', function(user, img) { chat._displayImage(user, img); }); this._initialEmoji(); }, _displayNewMsg: function(user, msg, color) {//显示消息 var container = $('#historyMsg'), msgToDisplay = '', date = new Date().toTimeString().substr(0, 8), default_color = color || '#000'; msg = this._showEmoji(msg); msgToDisplay = '<p style="color:'+color+'">'+user + '<span class="timespan">(' + date + '): </span>' + msg+'</p>'; container.append(msgToDisplay); container.scrollTop = container.scrollHeight; }, _displayImage: function(user, imgData, color) { var container = $('#historyMsg'), msgToDisplay = '', date = new Date().toTimeString().substr(0, 8), mycolor = color || '#000'; msgToDisplay = '<p style="color:'+color+'">'+ user + '<span class="timespan">(' + date + '): </span> <br/>' + '<a href="' + imgData + '" target="_blank"><img src="' + imgData + '"/></a></p>'; container.append(msgToDisplay); container.scrollTop = container.scrollHeight; }, _initialEmoji: function() { var emojiContainer = $('#emojiWrapper'), docFragment = ''; for (var i = 69; i > 0; i--) { var emojiItem = '<img '; emojiItem += ' src="content/emoji/' + i + '.gif" '; emojiItem += 'title="'+i+'" >'; docFragment+=emojiItem; }; emojiContainer.append(docFragment); }, _showEmoji: function(msg) { var match, result = msg, reg = /\[emoji:\d+\]/g, emojiIndex, totalEmojiNum = $('#emojiWrapper').children().length; while (match = reg.exec(msg)) { emojiIndex = match[0].slice(7, -1); if (emojiIndex > totalEmojiNum) { result = result.replace(match[0], '[X]'); } else { result = result.replace(match[0], '<img class="emoji" src="content/emoji/' + emojiIndex + '.gif" />'); }; }; return result; } };
server.js
var express = require('express'), app = express(), server = require('http').createServer(app), io = require('socket.io').listen(server), users =[]; app.use('/',express.static(__dirname+'/www/')); server.listen(8080); io.on('connection',function(socket){ socket.on('login',function(nickname){ if(users.indexOf(nickname)>-1){ socket.emit('nickExisted'); }else{ socket.userIndex = users.length; socket.nickname = nickname; users.push(nickname); socket.emit('loginSuccess'); io.sockets.emit('system', nickname, users.length, 'login'); } }); socket.on('disconnect', function() { users.splice(socket.userIndex, 1); socket.broadcast.emit('system', socket.nickname, users.length, 'logout'); }); socket.on('postMsg', function(msg,color) { //将消息发送到除自己外的所有用户 socket.broadcast.emit('newMsg', socket.nickname, msg,color); }); //接收用户发来的图片 socket.on('img', function(imgData) { //通过一个newImg事件分发到除自己外的每个用户 socket.broadcast.emit('newImg', socket.nickname, imgData); }); }); console.log('server started');
部署上线
先添加一个node.js程序通用的package.json文件
指定程序使用了哪些模块,其他人在获取到代码后,只需通过npm install命令就可以自己下载安装程序中需要的模块了
package.json
{ "name": "hichat", "description": "a realtime chat web application", "version": "0.4.0", "main": "server.js", "dependencies": { "express": "4.15.x", "socket.io": "1.7.x" }, "engines": { "node": "0.12.x", "npm": "2.15.x" } }
注:
由于是onchange事件时,发送图片,所以连续发同一张图的话就没法发送了
方法一:
$("#file").val("");
方法二:
var file = $("#file");
file.after(file.clone().val(""));
file.remove();
但是如果是直接在onchang事件里写的话
$('#sendImage').change(function() { //检查是否有文件被选中 if (this.files.length != 0) { //获取文件并用FileReader进行读取 var file = this.files[0], reader = new FileReader(); if (!reader) { chat._displayNewMsg('system', '!your browser doesn\'t support fileReader', 'red'); this.value = ''; return; }; reader.onload = function(e) { //读取成功,显示到页面并发送到服务器 this.value = ''; chat.socket.emit('img', e.target.result); chat._displayImage('me', e.target.result); }; reader.readAsDataURL(file); $("#sendImage").val(""); //var file_obj = $("#sendImage"); // file_obj.after(file_obj.clone().val("")); //file_obj.remove(); } });
如果直接这样写,方法二是不好使的,方法一没问题
可以将index.html此处修改
<input id="sendImage" type="file" value="image" onchange="changeImg(this)"/>
chat.js去掉$('#sendImage').change
增加
function changeImg(e){ //检查是否有文件被选中 if (e.files.length != 0) { //获取文件并用FileReader进行读取 var file = e.files[0], reader = new FileReader(); if (!reader) { chat._displayNewMsg('system', '!your browser doesn\'t support fileReader', 'red'); e.value = ''; return; }; reader.onload = function(e) { //读取成功,显示到页面并发送到服务器 e.value = ''; chat.socket.emit('img', e.target.result); chat._displayImage('me', e.target.result); }; reader.readAsDataURL(file); } $("#sendImage").val(""); /* var file_obj = $("#sendImage"); file_obj.after(file_obj.clone().val("")); file_obj.remove(); */ }
这样一个简单的聊天就写好了,这是广播的形式发送消息,其他的还有待完善