最简单的聊天室
我们将使用 socket.io 和 Angular.js 从零开始,搭建一个多人的多房间聊天室。将会带着大家使用 socket.io 和 Angular 实现一个单页应用(SPA),通过本章的学习,读者将会了解如何把 node 与前端的开发框架结合起来,体会前端开发流程,快速实现 Web 应用。
socket.io 简介
HTML5 引入了很多新的特性,WebSocket 就是其中之一,它为浏览器端和服务器端提供了一个基于 TCP 链接的双向通道,这样 Web 开发人员可以使用 WebSocket 构建真实的实时 Web 应用。但是并不是所有的浏览器都支持 WebSocket 特性,在不支持 WebSocket 的浏览器中,我们可以使用一些其他的方法来实现实时通信,例如:轮询、长轮询、基于流或者 Flash Socket 的实现。socket.io 出现就是为了磨平浏览器的差异,为开发者提供一个统一的接口,在不支持 WebSocket 的浏览器中,socket.io 可以降级为其他通信方式来实现实时通信。下面是 socket.io 所使用的实时通信方式列表:
- Websocket
- Adobe® Flash® Socket
- AJAX long polling
- AJAX multipart streaming
- Forever Iframe
- JSONP Polling
在开发过程中,我们甚至可以指定使用某种通信方式。
Angular.js
Angular.js 是新一代前端 MVC 框架。与 Backbone.js 相比,它完全实现了数据层和视图层的双向绑定,开发人员可以专注于功能开发,而无需纠缠在繁琐的 DOM 操作之中,这也正式选择它的原因。除此之外,Angular.js 社区非常活跃,有大量的文档和组件。废话休说,让我开始吧!
开始
新建 TechNode
目录,使用 npm init
初始化, 生成 package.json
文件:
$ mkdir TechNode && cd TechNode && npm init
有了 package.json 文件,我们就可以轻松地管理项目的依赖。
使用 express 搭建服务器
想必你应该很清楚如何使用 express 搭建服务器了,下面是 TechNode 的服务器,文件名为 app.js,直接放在 TechNode 下面:
var express = require('express')
var app = express()
var port = process.env.PORT || 3000
app.use(express.static(__dirname + '/static'))
app.use(function (req, res) {
res.sendfile('./static/index.html')
})
var io = require('socket.io').listen(app.listen(port))
io.sockets.on('connection', function (socket) {
socket.emit('connected')
})
console.log('TechNode is on port ' + port + '!')
我们将静态文件放在 staitc
目录下,包括 index.html
,我们不使用 jade
来生成 html 页面,而是直接使用 html 文件。
app.use(express.static(__dirname + '/static'))
除了静态文件的请求意外,其他所有的 HTTP 请求,我们都转发到 index.html
,服务端不关心路由,所有的路由逻辑都交给客户端的 Angular.js 去处理。
app.use(function (req, res) {
res.sendfile('./static/index.html')
})
接下来就是建立服务端与浏览器端的 socket 服务。socket.io 的 API 是基于事件的,我们监听着 connected
事件,如果有客户端链接上来,我们就可以获得一个 socket 对象,这样我们就可以和这个客户端实时通信了。
var io = require('socket.io').listen(app.listen(port))
io.sockets.on('connection', function (socket) {
socket.emit('connected')
})
新建 static
目录,添加 index.html
文件:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TechNode</title>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
</head>
<body>
<script type="text/javascript">
var socket = io.connect('/')
socket.on('connected', function () {
alert('connected to TechNode!')
})
</script>
</body>
</html>
为了与服务端的 socket.io 通信,我们必须在客户端引入 socket.io 提供的客户端代码 socket.io.js
。这个文件并不是我们手动添加到 static
目录的,socket.io 自己响应了这个请求。当然,你可以将这个文件保存下来,从别的地方引用。
var socket = io.connect('/')
socket.on('connected', function () {
alert('connected to TechNode!')
})
调用 io.connect
,传入 socket 服务器,因为在本例中,socket 服务器与静态服务器是同一个,可以简写为 /
(实际上相当于 http://localhost:3000
),获得一个 socket 对象,然后就可以和服务端通信了。
别忘了使用 npm install express socket.io --save
安装 express
和 socket.io
,参数 --save
可以自动更新 package.json 文件,将 express 和 socket.io 的依赖加进去。
服务器搭建好了,运行:
$ node app.js
TechNode is on port 3000!
,访问 http://localhost:3000
,试试看。
最简单的聊天室
使用 Bootstrap 和 Angular.js
我们使用 bower
来做前端类库的管理,首先新建 bower.json
文件,用来管理 TechNode 前端依赖的第三方库,运行 bower init
命令,回答一些问题,可以自动帮你生成 bower.json 文件。
新建.bowerrc 文件,添加如下内容,为 bower 指定包的安装目录:
{
"directory" : "static/components"
}
接下来,试用 bower
安装我们需要的一些前端组件:
- bootstrap:快速构建 web 项目的前端 UI 库,包含了 jquery;
- angular:我们的主角,前端 MVC 框架。
bower install bootstrap angular --save
将这些类库引入到 index.html 中:
<head>
<meta charset="UTF-8">
<title>TechNode</title>
<link rel="stylesheet" href="/components/bootstrap/dist/css/bootstrap.min.css">
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="/components/jquery/jquery.js"></script>
<script type="text/javascript" src="/components/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/components/angular/angular.js"></script>
</head>
搭建聊天室
使用 html 和 angular 将 TechNode 的外观搭建出来:
<head>
...
<link rel="stylesheet" href="/styles/room.css">
...
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">TechNode</a>
</div>
</div>
</div>
<div class="container" style="margin-top:100px;">
<div class="col-md-12">
<div class="panel panel-default room">
<div class="panel-heading room-header">TechNode</div>
<div class="panel-body room-content">
<div class="messages">
<div class="list-group">
</div>
</div>
<form class="message-creator">
<div class="form-group">
<textarea required class="form-control message-input" placeholder="Ctrl+Enter to quick send"></textarea>
</div>
</form>
</div>
</div>
</div>
</div>
...
新建 styles 文件夹,将房间的样式放到 room.css 文件中,最简单的 TechNode 聊天室就像下面这样:
我们首先实现聊天室最简单的功能——发送消息和显示消息!
修改 app.js 中 socket 通信的部分:
// ...
var messages = []
io.sockets.on('connection', function (socket) {
socket.on('messages.read', function () {
socket.emit('messages.read', messages)
})
socket.on('messages.create', function (message) {
messages.push(message)
io.sockets.emit('messages.add', message)
})
})
// ...
我们是先使用 messages 这个内存对象来存放消息内容。 用户连上来后,向服务器发送 messages.read 的请求,获取消息,我们将所有的消息返回给用户; 当用户新建消息时,messages.create,我们把消息存放到 messages 中,并向所有的客户端广播有消息添加进来了。
下一步,开始使用 angular 来构建我们的聊天室吧。
修改 index.html,加上 angular 了的绑定,添加一个新的 js 文件 technode.js 到页面上:
<!doctype html>
<html ng-app="techNodeApp">
<head>
...
</head>
<body>
...
<div class="container" style="margin-top:100px;">
<div class="col-md-12">
<div class="panel panel-default room" ng-controller="RoomCtrl">
<div class="panel-heading room-header">TechNode</div>
<div class="panel-body room-content">
<div class="list-group messages" auto-scroll-to-bottom>
<div class="list-group-item message" ng-repeat="message in messages">
某某: {{message}}
</div>
</div>
<form class="message-creator" ng-controller="MessageCreatorCtrl">
<div class="form-group">
<textarea required class="form-control message-input" ng-model="newMessage" ctrl-enter-break-line="createMessage()" placeholder="Ctrl+Enter to quick send"></textarea>
</div>
</form>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="/technode.js"></script>
</body>
</html>
在 index.html 中,我们做了一些添加 angular 的申明;
- ng-app="techNodeApp":申明了一个名为 techNodeApp 的 angular 应用,该应用的所有逻辑将来 technode.js 这个文件中定义;
- ng-controller="RoomCtrl": 申明了一个 Room 组件,它的 Controller 是 RoomCtrl;
- ng-controller="MessageCreatorCtrl": 另外一个 controller,用来控制发送消息组件;
- ng-repeat="message in messages": 一个 repeat 绑定,将从服务器端读过来的 messages 显示在页面中,angluar 的数据绑定是双向的,所以当 messages 中的消息变化时,显示给用户的信息也会跟着变化。
新建 technode.js 文件,放到 static 目录下,来看看 technode.js 的实现:
申明了一个 techNodeApp,与页面中的 ng-app 绑定对应;
angular.module('techNodeApp', [])
为了与服务器通信,我们将 socket.io 封装成了一个 angular 的服务 socket,接下来我们就可以很方便的与服务端通信了;
angular.module('techNodeApp').factory('socket', function($rootScope) {
var socket = io.connect('/')
return {
on: function(eventName, callback) {
socket.on(eventName, function() {
var args = arguments
$rootScope.$apply(function() {
callback.apply(socket, args)
})
})
},
emit: function(eventName, data, callback) {
socket.emit(eventName, data, function() {
var args = arguments
$rootScope.$apply(function() {
if (callback) {
callback.apply(socket, args)
}
})
})
}
}
})
接下来是定义我们 RoomCtrl,它的职责就是当 techNode 启动时,通过 socket 从服务端将聊天室的全部消息读出来;如果服务端有新消息进来,就把消息添加到 messages 中。由于 angular 的绑定机制,我们完全不用去操作 DOM,messages 的变化会动态地反映在页面上。
angular.module('techNodeApp').controller('RoomCtrl', function($scope, socket) {
$scope.messages = []
socket.on('messages.read', function (messages) {
$scope.messages = messages
})
socket.on('messages.add', function (message) {
$scope.messages.push(message)
})
socket.emit('messages.read')
})
MessageCteatorCtrl 的定义也非常简单,当用户按下回车时,将消息通过 socket 发送给服务端;注意着了的 newMessage 是通过 ng-model 与 textarea 直接绑定的;
angular.module('techNodeApp').controller('MessageCreatorCtrl', function($scope, socket) {
$scope.createMessage = function () {
socket.emit('messages.create', $scope.newMessage)
$scope.newMessage = ''
}
})
接下来是 angular 的两个辅助组件:
- autoScrollToBottom:当消息很多出现滚动条时,该组件使得滚动条能随着消息的增加自动滚动到底部;
- ctrlEnterBreakLine: 在 textarea 回车,默认会换行,使用这个组件,可以通过 ctrl+enter 来换行,而 enter 则触发绑定的行为,在这里就是 createMessage 这个方法。
angular.module('techNodeApp').directive('autoScrollToBottom', function() {
return {
link: function(scope, element, attrs) {
scope.$watch(
function() {
return element.children().length;
},
function() {
element.animate({
scrollTop: element.prop('scrollHeight')
}, 1000);
}
);
}
};
});
angular.module('techNodeApp').directive('ctrlEnterBreakLine', function() {
return function(scope, element, attrs) {
var ctrlDown = false
element.bind("keydown", function(evt) {
if (evt.which === 17) {
ctrlDown = true
setTimeout(function() {
ctrlDown = false
}, 1000)
}
if (evt.which === 13) {
if (ctrlDown) {
element.val(element.val() + '\n')
} else {
scope.$apply(function() {
scope.$eval(attrs.ctrlEnterBreakLine);
});
evt.preventDefault()
}
}
});
};
});
开始与好友匿名聊天!
一个最简单的聊天室搭建完成了,说简单,因为它没有用户的概念,大家完全就是匿名的,不过,这个简单的聊天室,让我们了解一个聊天室的全貌,名来了如何结合 angular 和 socket.io 的威力快速搭建应用。
好了,下一步我们就开始加入用户的功能!让大家不再匿名!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论