准备工作
- 安装Node.js
从官网下一个装上就行了,不表。 - 安装
express
Web 框架
- 在项目文件夹下创建
package.json
文件和其他支持文件
npm init
- 安装并将
express
其保存在依赖项列表中
npm install --save express
- 安装并将
socket.io
其保存在依赖项列表中,作为实时应用提供跨平台实时通信的库
npm install --save socket.io
- 安装时请务必注意有没有报错,如果报错了没装上,后面是会出现未定义io什么的错误的。
如果出现 'No repository field.' 的错误,请修改package.json
,添加"private": true
,如下:
{
"name": "my-super-amazing-app",
"version": "1.0.0",
"private": true
}
- 按照以下目录创建项目文件
root/
- index.js
- package.json
- public/
- index.html
- main.js
- style.css
- node_modules/
- ...
搭建本地服务器
在index.js
中添加:
var express = require('express');
var app = express();
var http = require('http').Server(app);
http.listen(3000, function(){
console.log('listening on *:3000');
});
终端运行node index.js
,将会看到以下输出:
创建服务器与html、html与js/css的关联
在index.js
开头添加:
var path = require('path');
在index.js
末尾添加:
//设定访问地址时所需加载文件的路径,其中__dirname指向当前js代码文件的目录
//可自行根据目录安排调整路径
app.get('/', function(req, res){
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
//设定资源目录,为了html能够正常引入js和css等静态文件
//这里如果不设置或者目录有问题,css和js都是加载不上的
//只能看见一个光秃秃的html
app.use(express.static(path.join(__dirname, 'public')));
在index.html
中引入style.css
:
<link rel="stylesheet" href="/style.css">
在index.html
中引入main.js
:
<script src="/main.js"></script>
完成UI界面
页面说明:
index.html
代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>chat</title>
<link rel="stylesheet" href="/style.css">
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
</head>
<body>
<div class="wrapper">
<div class="banner">
<h3>聊天室</h3>
<span id='status' style='color: #ff6347'></span>
</div>
<div id='historyMsg'>
</div>
<div class="controls">
<textarea id="messageInput" placeholder='输入消息'></textarea>
<input id="sendBtn" type="button" value="发送">
</div>
</div>
<div id="loginWrapper">
<p id='info'>正在连接到服务器...</p>
<div id="nickWrapper">
<input type="text" placeholder="请输入昵称" id="nicknameInput">
<input type="button" id="loginBtn" value="登录">
</div>
</div>
<!--【【 高亮 】】一定要引入socket.io.js,不然main.js会报错-->
<script src="/socket.io/socket.io.js"></script>
<script src="/main.js"></script>
</body>
</html>
style.css
代码:
html, body{
margin: 0;
background-color: #efefef;
font-family: sans-serif;
}
.wrapper{
width: 500px;
height: 520px;
padding: 5px;
margin: 0 auto;
background-color: #ddd;
}
#loginWrapper{
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
background-color: rgba(5, 5, 5, 0.6);
text-align: center;
color: #fff;
display: block;
padding-top: 200px;
}
#nickWrapper{
display: none;
}
.banner{
height: 70px;
width: 100%;
text-align: center;
}
.controls{
height: 100px;
margin: 5px 0px;
position: relative;
}
#historyMsg{
height: 300px;
background-color: #fff;
overflow: auto;
padding: 2px;
}
.timespan{
color: #ddd;
}
#messageInput{
width: 440px;
height: 90px;
max-height: 90px;
float: left;
}
#sendBtn{
width: 50px;
height: 96px;
float: left;
}
事件设计
建议对照后面的实际事件查看。
登录流程 发消息流程
完成客户端 main.js
(最麻烦的部分来了,在这里将会创建一系列的事件,供本页面的操作、服务器的操作调用)
- 首页在页面加载的时候实例并初始化一个chat的程序,然后调用chat的init方法运行程序
window.onload = function() {
//实例并初始化chat程序
var hichat = new Chat();
hichat.init();
};
- 定义Chat类,在这个对象里面将socket初始化
var Chat = function() {
this.socket = null;
};
- 向Chat类添加两个方法
Chat.prototype = { //向Chat类添加方法
init: function() { //初始化方法 },
_displayNewMsg: function (user, msg, color) { //展示新消息的方法 },
};
- 完成init方法
(以下内容均为init方法内的代码)
4.1 定义that变量,并将this赋值给that,供事件内部使用
var that = this;
4.2 建立到服务器的socket连接
this.socket = io.connect();
4.3 设置客户端的预置事件,'connect'事件和'error'的事件(事件名都是预置的,不能改)
设置监听socket的'connect'的事件:
此事件表示成功与服务器创建连接后的行为。
行为为将p#info的文本变成"请输入昵称",将div#nickWrapper(昵称输入部分)由不可见变为可见,昵称输入框获取焦点。
this.socket.on('connect', function(){
$('#info').text('请输入昵称');
$('#nickWrapper').css({
"display":"block",
});
$('#nicknameInput').focus();
});
设置监听socket的'error'的事件:
此事件表示与服务器连接失败后的行为。
根据是否已经成功登录,分别在不同的位置提示连接失败。
this.socket.on('error', function(err) {
if ($('#loginWrapper').css('display') == 'none') {
$('#status').text('连接失败');
}
else {
$('#info').text('!连接失败');
}
});
4.4 设置客户端自定义事件
设置监听socket的、名为'nickExisted'的事件:
此事件在判定用户输入的昵称是否被占用时使用。
用户输入昵称后上传到服务端,如果服务端发现昵称已被占用,则触发此事件,将结果返回到客户端,并将p#info的文本变成"昵称已被占用"。
this.socket.on('nickExisted', function() {
$('#info').text('昵称已被占用!');
});
设置监听socket的、名为'loginSuccess'的事件:
此事件表示成功登录聊天室后的行为。
登录成功后整个弹窗部分隐藏,聊天内容输入框获取焦点。
this.socket.on('loginSuccess', function() {
$('#loginWrapper').css({
"display":"none",
});
$('#messageInput').focus();
});
设置监听socket的、名为'system'的事件:
服务端有人登录的时候触发此事件,服务端将在线人数通知到客户端,并在span#status中展示出来。
this.socket.on('system', function(nickName, userCount, type){
var msg = nickName +(type == 'login' ? 'joined' : 'Left');
that._displayNewMsg('system ', msg, 'red');
$('#status').text(userCount +'用户在线');
});
设置监听socket的、名为'newMsg'的事件:
服务端收到新消息时触发此事件。服务端向客户端广播消息的内容供客户端展示使用。
this.socket.on('newMsg', function(user, msg, color) {
that._displayNewMsg(user, msg, color);
});
4.5 为前端页面绑定事件
为登录按钮绑定事件:
当用户点击登录按钮时,如果输入的昵称为非空时,触发服务端的'login'事件。输入昵称为空时继续获取焦点,但不提交。
$('#loginBtn').bind('click',function(){
var nickName = $('#nicknameInput').val(); //获取输入框内的昵称
if (nickName.trim().length != 0){ //判定内容去除空格后是否为空
that.socket.emit('login', nickName);
}
else{
$('#nicknameInput').focus();
}
});
为发送消息的按钮绑定事件:
当用户点击发送消息按钮时,如果发送内容不为空,则触发服务端的'postMsg'事件,并且在本客户端展示刚发出的消息。
$('#sendBtn').bind('click', function() {
var msg = $('#messageInput').val(); //获取输入框内的消息
var color = '#000'; //由于是用户发出的内容,所以设置为#000色
$('#messageInput').val(''); //清空输入框
$('#messageInput').focus();
if (msg.trim().length != 0) { //判定内容去除空格后是否为空
that.socket.emit('postMsg', msg, color);
that._displayNewMsg('我', msg, color);
return;
};
});
- 完成'_displayNewMsg'方法
_displayNewMsg: function (user, msg, color) { //展示新消息的方法
var msgToDisplay = $("<p></p>");
var date = new Date().toTimeString().substr(0, 8); //获取当前的时间
msgToDisplay.css({
"color":color, //消息字体的颜色
});
msgToDisplay.html(user + '<span class="timespan">(' + date + '): </span>' +msg);
$('#historyMsg').append(msgToDisplay); //向消息列表插入子元素
$('#historyMsg').scrollTop = $('#historyMsg').scrollHeight; //超出窗口高度酒有滚动条
},
补全服务端 index.js
- 补充引入socket.io,及定义用户数组。
现在index.js
的最上方应该是这样的:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var path = require('path');
var io = require('socket.io')(http);
var users = [];
- 创建服务端事件
在io.on('connection', function(socket){});
事件中,console.log('a user connected');
的下方创建服务端的一系列事件。
2.1 设置服务端的预置事件,'disconnect'事件
(事件名都是预置的,不能改)
设置监听socket的'disconnect'的事件:
如果离线用户昵称不为空,则将数组中相应的用户删除,并向其他用户广播。
socket.on('disconnect', function(){
if (socket.nickname != null){
users.splice(users.indexOf(socket.nickname),1);
socket.broadcast.emit('system', socket.nickname, users.length, 'logout');
}
});
2.2 设置服务端自定义事件
设置监听socket的、名为'login'的事件:
如果昵称重复,则触发客户端的'nickExisted'事件。
如果昵称不重复,则将昵称加入数组,并触发客户端的'loginSuccess'事件及'system'事件。
socket.on('login', function(nickname){
if (users.indexOf(nickname)>-1){
socket.emit('nickExisted');
}
else{
socket.nickname = nickname;
users.push(nickname);
socket.emit('loginSuccess');
io.emit('system', nickname, users.length, 'login');
}
});
设置监听socket的、名为'postMsg'的事件:
触发本事件时,服务端触发客户端的'newMsg'事件,并向其他用户广播。
socket.on('postMsg', function(msg, color) {
socket.broadcast.emit('newMsg' ,socket.nickname, msg, color);
});
-
socket.emit
、io.emit
(io.sockets.emit
)、socket.broadcast.emit
的区别
-
socket.emit
对触发的客户端生效 -
io.emit
(io.sockets.emit
) 对在线的所有客户端生效 -
socket.broadcast.emit
对除了触发的客户端以外的所有客户端生效
测试
- 终端运行
node index.js
- 打开浏览器访问 http://localhost:3000/
网友评论