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

  

  

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

posted @ 2017-03-17 09:50  慕尘  阅读(1503)  评论(0编辑  收藏  举报