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();
*/
}

  

  

这样一个简单的聊天就写好了,这是广播的形式发送消息,其他的还有待完善

node.js和socket.io实现im的更多相关文章

  1. Node.js 和Socket.IO 实现chat WEBIM

    socket官方:   http://socket.io/  需求:实现WEB IM功能,数据从服务器PUSH  不是PULL  websocket是基于HTML5的新特性,不兼容IE6,7,8 .. ...

  2. 使用Node.js的socket.io模块开发实时web程序

    首发:个人博客,更新&纠错&回复 今天的思维漫游如下:从.net的windows程序开发,摸到nodejs的桌面程序开发,又熟悉了一下nodejs,对“异步”的理解有了上上周对操作系统 ...

  3. node.js和socket.io纯js实现的即时通讯实例分享

    在这个例子中,其实node.js并没有真正起到服务器的作用,因为我们这里可以直接运行client.html文件,而不用输入url请求,当 然,要想输入url请求页面内容还需要加入请求静态文件的代码.这 ...

  4. [Node.js] 基于Socket.IO 的私聊

    原文地址:http://www.moye.me/2015/01/02/node_socket-io/ 引子 最近听到这么一个问题:Socket.IO 怎么实现私聊?换个提法:怎么定位到人(端),或者说 ...

  5. Node.js + Web Socket 打造即时聊天程序嗨聊

    前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...

  6. Node+Express+MongoDB + Socket.io搭建实时聊天应用

    Node+Express+MongoDB + Socket.io搭建实时聊天应用 前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战 ...

  7. Node+Express+MongoDB + Socket.io搭建实时聊天应用实战教程(二)--node解析与环境搭建

    前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战.写教程一方面在自己写的过程中需要考虑更多的东西,另一方面希望能对node入门者有 ...

  8. Node+Express+MongoDB+Socket.io搭建实时聊天应用实战教程(一)--MongoDB入门

    前言 本文并不是网上流传的多少天学会MongoDB那种全面的教程,而意在总结这几天使用MongoDB的心得,给出一个完整的Node+Express+MongoDB+Socket.io搭建实时聊天应用实 ...

  9. 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制

    转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...

随机推荐

  1. Ubuntu 12.04 LTS(64 bit) + RTL8188CU无线网卡驱动

    . . . . . 之前家里台式机上安装的是win7+CentOS双系统,但是CentOs的无线网卡驱动不知为何无论如何都安装不上,再加上这段时间一直很忙,所以一直使用着win.这几天事情忙得差不多了 ...

  2. Java中ArrayList实现原理

    简述: ArrayList可以理解为动态数组,与Java中的数组相比,它的容量能动态增长.超出限制时会增加50%容量,用System.arraycopy()复制到新的数组中,因此最好能给出数组大小的预 ...

  3. 【Unity Shader】四、高光反射Specular Shader例子

    http://www.cnblogs.com/guxin/p/unity-diffuse-shader-demo.html 在上文中已经学习了漫反射Diffuse Shader和环境光,现在再在此基础 ...

  4. MySQL的MVCC

    基本概念 Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问:在编程语言中实现事务内存. ...

  5. virtualbox 安装 USB 扩展功能

    virtualbox USB 扩展包下载 扩展包下载地址: http://download.virtualbox.org/virtualbox/ 选择你的 virtualbox 版本 看版本在 vir ...

  6. TP v5中Url Compat模式

    compatible 对于配置pathinfo的支持,在Nginx作服务器.无数种系统要同时运行的环境,实在是一项很累赘的事情,而又不想很low的多个参数(像m.c.a)构造路由参数,我需要那种不必强 ...

  7. 由于无法验证发布者 所以windows阻止此软件

    打开Internet Explorer---菜单栏点“工具”---Internet选项--安全---自定义级别---安全设置---“ActiveX控件和插件下”的第5个“下载未签名的ActiveX控件 ...

  8. linq to sql之组装where条件下的'或'语句

    之前遇到过类似的需求,即前台传入几个过滤条件,后台动态组装where. 例如,前台传入name='张三',age=10, 其余的字段,类似email,QQ之类的本次查询时不做过滤. 用linq to ...

  9. 优雅的运用 Kotlin 的 null safety 特性,而不要简单的直接用 !!双感叹号

    对于 Null 的检查是 Kotlin 的特点之一.强制你在编码过程中考虑变量是否可为 null,因此可以避免很多在 Java 中隐藏的 NullPointerException. 但是,当你用插件直 ...

  10. HTML5规范尘埃落定,5个开发工具推荐

    HTML5规范终于在今年正式定稿,对于从事多年HTML5开发的人员来说绝对是一个重大新闻.数字天堂董事长,DCloud CEO王安也发表了文章,从开发者和用户两个角度分析了HTML对两个人群的优势.其 ...