Git Product home page Git Product logo

chatroom's Introduction

Node一个经典的应用场景之一便是实时聊天程序,得益于HTML5定义的 websocket协议,服务器和客户端可以建立双向连接通道,以事件的方式,实现双向通信,客户端可以主动提交消息给服务端,服务端也可以主动推送消息给客户端。然而并不是每个浏览器都支持 websocket 协议,这便诞生了 Socket.io。

Socket.IO是一个开源的WebSocket库,它基于Node.js环境实现WebSocket服务端,同时也提供客户端JS库,可以工作在任何平台,如浏览器、手持设备等。

Socket.IO支持4种协议:WebSocket、htmlfile、xhr-polling、jsonp-polling,它会自动根据浏览 器选择适合的通讯方式,从而让开发者可以聚焦到功能的实现而不是平台的兼容性,同时Socket.IO具有不错的稳定性和性能。

本文使用 Node的 express 框架 + Socket.io 实现简易版聊天程序。 ##当前执行环境 node v5.1.0
npm 3.3.12
package.json 内容:

{
  ...
  "dependencies": {
    "cookie-parser": "^1.4.1",
    "express": "^4.13.4",
    "socket.io": "^1.4.5"
  }
  ...
}

##项目架构 ├── app.js ├── home.html ├── package.json ├── public ├── js └──cookie.js cookie.js 封装了一个全局对象 Cookie,用来读写 cookie, cookie.js 的完整内容可以查看文章最后的源码文件。 app.js 是项目的入口文件。 home.html 是聊天室静态 html 页面 ##自定义事件 'nickname' 为客户端推送和服务端监听的昵称事件; 'nicknames' 为服务端推送和客户端监听的全体昵称事件; 'message' 为客户端推送和监听,服务端推送的聊天消息事件;

##服务端程序逻辑 app.js 文件完整内容如下:

var http = require('http');
var express = require('express');
var path = require('path');
var app = express();
var nicknames = []; // 存储所有在线的用户昵称列表

// 创建 websocket 服务
var server = http.createServer(app);
var io = require('socket.io')(server);

// 托管静态目录
app.use(express.static(path.join(__dirname, 'public')));

var port = process.env.PROT || 3000;

// 路由 响应 http:127.0.0.1:3000/ 请求
app.get('/', function(req, res) {
    res.sendFile('home.html', { root: __dirname, dotfiles: 'deny' });
})

// 监听socket连接
io.on('connection', function(socket) {
    console.log('socket connected');
    // 广播所有在线的用户
    io.emit('nicknames', nicknames);
    
	// 监听 nicname 事件 校验客户端推送过来的 nickname 
	socket.on('nickname', function(data, callback) {
		callback = callback || function(){};
	    // nickname 已经存在 返回错误信息
	    if (nicknames.indexOf(data) > -1) {
	        callback({ msg: '昵称已经存在' });
	        return;
	    }
	    // nickname 不存在,保存当前 nickname 至当前 socket
	    socket.nickname = data;
	    nicknames.push(data);
	    
	    // 推送返回设置成功的信息给客户端
	    callback({
	        msg: 'ok'
	    });
	
	    // 广播当前 nickname列表给所有客户端 包括当前sender
	    io.emit('nicknames', nicknames);
	    console.log(nicknames);
	})
	
	// 监听 客户端消息推送
	socket.on('message', function(data) {
	    io.emit('message', {
	        nickname: data.nickname,
	        msg: data.msg
	    });
	})
	
	// 监听 客户端 socket 断开连接
	socket.on('disconnect', function() {
	    if (!socket.nickname) {
	        return;
	    }
	    // 若客户端断开连接 清除当前存储在 socket 上的 nickname 属性
	    // 清除存储中的 nickname
	    if (nicknames.indexOf(socket.nickname) > -1) {
	        nicknames.splice(nicknames.indexOf(socket.nickname), 1);
	        delete socket.nickname;
	    }
	    // 广播nicknames 变动
	    io.emit('nicknames', nicknames);
	})
})

// 服务器监听端口
server.listen(port);
console.log('server run ok');

分步说明核心逻辑:

1、启用 socket服务

...
var express = require('express');
var app = express();
...
// 创建 websocket 服务
var server = http.createServer(app);
var io = require('socket.io')(server);
...

2、监听和推送核心逻辑 核心逻辑主要分2块,第一块跟昵称相关的设置昵称和广播昵称列表,第二块聊天 昵称相关的主要思路如下:
1)socket连接成功,推送当前用户列表给客户端

// 监听socket连接
io.on('connection', function(socket) {
    //...
    socket.emit('nicknames', nicknames);
    // ...
})

2)监听客户端推送过来的 nickname 事件,先检测当前设置的 nickname已经存在于服务器(即被他人使用),则利用 socket 的事件回调,返回错误信息告知客户端昵称设置失败;反之,缓存当前昵称至全局的 nicknames 数组,同时将该数组广播给所有客户端(包括当前连接的客户端)

io.on('connection', function(socket) {
    //...
    // 监听 nicname 事件 校验客户端推送过来的 nickname 
    socket.on('nickname', function(data, callback) {
    	callback = callback || function(){};
        // nickname 已经存在 返回错误信息
        if (nicknames.indexOf(data) > -1) {
            callback({ msg: '昵称已经存在' });
            return;
        }
        // nickname 不存在,保存当前 nickname 至当前 socket
        socket.nickname = data;
        nicknames.push(data);
        
        // 推送返回设置成功的信息给客户端
        callback({
            msg: 'ok'
        });

        // 广播当前 nickname列表给所有客户端 包括当前sender
        io.emit('nicknames', nicknames);
    })
    // ...
})

聊天相关的: 监听客户端推送过来的 message 事件,同时广播给所有在连的客户端

io.on('connection', function(socket) {
    //...
    // 监听 客户端消息推送
    socket.on('message', function(data) {
        io.emit('message', {
            nickname: data.nickname,
            msg: data.msg
        });
    })
    // ...
})

断开连接处理思路: 清除当前昵称,从昵称列表里面剔除已经断开连接的昵称,同时广播给所有客户端。

io.on('connection', function(socket) {
    //...
    // 监听 客户端 socket 断开连接
    socket.on('disconnect', function() {
        if (!socket.nickname) {
            return;
        }
        // 若客户端断开连接 清除当前存储在 socket 上的 nickname 属性
        // 清除存储中的 nickname
        if (nicknames.indexOf(socket.nickname) > -1) {
            nicknames.splice(nicknames.indexOf(socket.nickname), 1);
            delete socket.nickname;
        }
        // 广播nicknames 变动
        io.emit('nicknames', nicknames);
    })
    // ...
})

##编写静态聊天室页面 静态页面的逻辑比较简单,主要逻辑包括
1、验证和设置用户昵称,设置成功则存入 cookie 下次直接使用 cookie 中的昵称;
2、客户端推送信息给服务端,同时监听服务端推送来的聊天消息;
3、监听服务端推送过来的昵称列表,重新初始化右侧昵称列表面板;
代码用的原生 js,感兴趣可以自行换成其他 js 类库。

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
<style>
	html,body,div,section,form,button,input,ul,li,p,textarea { margin: 0; padding: 0; outline: none; }
	body { font-size: 12px;  } 
	li { list-style: none;  }
	p { line-height: 1.5;  }
	textarea,button { border: none; }
	form { margin: 10px 0 10px;  }
	.layout { overflow: hidden; padding: 10px; }
	.message-box,
	.user-box { border: 1px solid #ccc; height: 300px; overflow: auto; float: left;  }
	.message-box { width: 300px; }
	.user-box .tit,
	.user-box p { padding-left: 10px; }
	.message-list { height: 85%; border-bottom: 1px solid #ccc; padding: 5px;  }
	.user-box { width: 150px; }
	.msg { height: 15%; width: 80%; border-right: 1px solid #ccc; padding: 5px; }
	.send-btn { width: 18%; height: 15%; -webkit-appearance : none; background: none; }
	.none { display: none;  }
	.i-b { display: inline-block; vertical-align: middle;  }
	.mb-10 { margin-bottom: 10px;  } 
	.border-box { -webkit-box-sizing : border-box; -moz-box-sizing : border-box; -ms-box-sizing : border-box; box-sizing : border-box;}
	.tit { height: 25px; line-height: 25px; border-bottom: 1px solid #ccc; }
	.t-r { text-align: right;  }  
</style>
</head>
<body>
<div class="layout">
	<span id="nickname-txt" class="none mb-10"></span>
	<form  id="nickname-form">
		<input type="text" name="nickname" placeholder="请填写昵称" />
		<input type="submit" value="提交昵称">
		<span class="tip none"></span>
	</form>
	<div class="message-box border-box">
		<div class="message-list border-box"></div>
		<textarea name="msg" class="msg i-b border-box" placeholder="说点啥呗"></textarea>
		<button class="send-btn i-b">发送</button>
	</div>
	<div class="user-box border-box"></div>
</div>
<script src="/js/socket.io.js"></script>
<script src="/js/cookie.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function(){
	var socket = io.connect('127.0.0.1:3000');
	var form = document.querySelector('#nickname-form'),
		nicknameInput = document.querySelector('input[name=nickname]'),
		userBox = document.querySelector('.user-box'),
		msgBox = document.querySelector('.message-list'),
		tip = document.querySelector('.tip');

	var msg = document.querySelector('.msg');

	// 标记当前 nickname 是否验证通过
	var nicknameOk = false;
	var name = Cookie.get('nickname');

	// 尝试从 cookie 读取昵称 若不存在,则用户填写昵称
	if(!name){
		// 填写昵称
		form.addEventListener('submit', function(e){
			var v = nicknameInput.value.trim();
			e = e || event;
			e.preventDefault();

			if(!v){
				alert('请输入昵称');
				return;
			}

			// 推送当前昵称给服务器校验
			socket.emit('nickname', v, function(data){
				// 校验成功
				if(data && data.msg === 'ok'){
					showNickname(v);
					Cookie.set('nickname', v, 3);
					return;
				}
				// 校验失败
				tip.style.display = 'inline-block';
				tip.innerHTML = data.msg;
			});

		}, false);
	}
	// 若存在 昵称直接从 cookie 中读取 同时推送昵称给服务器
	else {
		showNickname(name);
		socket.emit('nickname', name);
	}

	// 发送信息
	document.querySelector('.send-btn').addEventListener('click', function(e){
		e = e || event;
		e.preventDefault();
		if(!nicknameOk){
			alert('先填个昵称呗');
			return;
		}

		if(!msg.value){
			alert('啥也不写发毛线呢');
			return;
		}

		// 推送消息给服务器
		socket.emit('message', {
			nickname : name,
			msg : msg.value
		});
	}, false);

	// 监听服务器推送过来的消息
	socket.on('message', function(data){
		var node = document.createElement('p');
		if(data.nickname === name){
			node.classList.add('t-r');
		}
		node.innerHTML = data.nickname + "说:" + data.msg;
		prepend(msgBox, node);
		msg.value = '';
	})

	// 监听当前 nicknames列表
	socket.on('nicknames', function(data){
		console.log('nicknames', data);
		var html = '<div class="tit">在线成员:<span">(<%=num%>)</span></div>'.replace(/<%\=num%>/, data.length);
		data.forEach(function(v, i){
			html += '<p>' + v + '</p>';
		})
		userBox.innerHTML = html;
	})

	function showNickname(nickname){
		document.querySelector('#nickname-txt').style.display = 'block';
		document.querySelector('#nickname-txt').innerHTML = '您好,' + nickname;
		form.parentNode.removeChild(form);
		tip.innerHTML = '';
		nicknameOk = true;
		name = nickname;
	}


	function prepend(parentNode, node){
		if(parentNode.hasChildNodes()){
			parentNode.insertBefore(node, parentNode.firstChild);
		}
		else {
			parentNode.appendChild(node);
		}
	}
}, false);

</script>
</body>
</html> 

##如何使用 $ cd chatroom $ npm install $ node app.js

chatroom's People

Watchers

James Cloos avatar wangshangwen avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.