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页面
安装
1 | npm install express |
不用express需要将HTML代码与后台JavaScript代码写在一起进行请求的响应
1 2 3 4 5 6 7 8 9 10 | 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修改为
1 2 3 4 5 6 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!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连接,发送事件与接收特定事件
安装
1 | npm install socket.io |
安装后在node_modules文件夹下新生成了一个socket.io文件夹,其中可以找到一个socket.io.js文件
将它引入到HTML页面,这样我们就可以在前端使用socket.io与服务器进行通信了
通过socket.emit()来激发一个事件
通过socket.on()来侦听和处理对应事件
这两个事件通过传递的参数进行通信
查看安装的模块
eg:
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <!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事件并接收传递来的数据
1 2 3 4 5 6 7 8 9 10 11 12 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <!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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | <!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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | <!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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "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事件里写的话
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | $( '#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此处修改
1 | <input id= "sendImage" type= "file" value= "image" onchange= "changeImg(this)" /> |
chat.js去掉$('#sendImage').change
增加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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(); */ } |
这样一个简单的聊天就写好了,这是广播的形式发送消息,其他的还有待完善
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)