В этой статье мы собираемся создать простое приложение для чата, используя Socket.IO и Backbone.js . Socket.IO предназначен для разработчиков, которые хотят начать разработку интерактивных веб-приложений в режиме реального времени, таких как системы чата или многопользовательские игры. Backbone.js добавит структуру в наш клиентский код и упростит управление и разделение проблем в нашем приложении.
Читатели должны быть знакомы с Node.js и Express . Знакомство с Backbone является плюсом, а также Underscore.js , который используется для базовых шаблонов.
Вступление
Диаграмма, иллюстрирующая структуру нашего клиентского кода, показана ниже. В середине находится контроллер, который действует как мост между клиентом сокета и представлением. Контроллер получает обновления от клиента сокета и меняет модель. Обновления отражаются в представлении с использованием привязок Backbone.
Сторона клиента
Начнем с рассмотрения кода на стороне клиента. Все чаты обрабатываются в HomeView
Давайте начнем с определения HomeModel
/public/js/models/main.js
var HomeModel = Backbone.Model.extend({
defaults: {
// Backbone collection for users
onlineUsers: new UserCollection(),
// Backbone collection for user chats, initialized with a predefined chat model
userChats: new ChatCollection([
new ChatModel({sender: '', message: 'Chat Server v.1'})
])
},
// method for adding a new user to onlineUsers collection
addUser: function(username) {
this.get('onlineUsers').add(new UserModel({name: username}));
},
// method for removing a user from onlineUsers collection
removeUser: function(username) {
var onlineUsers = this.get('onlineUsers');
var u = onlineUsers.find(function(item) {
return item.get('name') == username;
});
if (u) {
onlineUsers.remove(u);
}
},
// method for adding new chat to userChats collection
addChat: function(chat) {
this.get('userChats').add(new ChatModel({sender: chat.sender, message: chat.message}));
},
});
Мы используем коллекции Backbone для прослушивания изменений в коллекции. Обновления в коллекциях отражаются автоматически нашим представлением.
Далее мы определяем наш домашний шаблон внутри /public/index.html
<script type="text/template" id="home-template">
<div class="row">
<div class="col-md-10">
<div class="panel panel-default">
<div class="panel-heading">Lobby</div>
<div class="panel-body">
<div class="nano">
<div class="content">
<div class="list-group" id="chatList"></div>
</div>
</div>
<form>
<input class="form-control" type="text" id="chatInput"></input>
</form>
</div>
</div>
</div>
<div class="col-md-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Online Users <span class="badge pull-right" id="userCount"></span></h3>
</div>
<div class="panel-body">
<div class="nano">
<div class="content">
<div class="list-group" id="userList"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</script>
HomeView
/public/js/views/main.js
Файл относительно длинный, поэтому читатель может его изучить.
Клиент чата
Далее давайте определим наш чат-клиент Socket.IO. Он связывается с сервером, отправляя сообщения на сервер и прослушивая уведомления. Эти уведомления запускают события на шине событий для связи с контроллером. Следующий код находится в /public/js/socketclient.js
var ChatClient = function(options) {
// redefine this to avoid conflicts
var self = this;
// app event bus
self.vent = options.vent;
// server hostname replace with your server's hostname eg: http://localhost
self.hostname = 'http://chatfree.herokuapp.com';
// connects to the server
self.connect = function() {
// connect to the host
self.socket = io.connect(self.hostname);
// set responseListeners on the socket
self.setResponseListeners(self.socket);
}
// send login message
self.login = function(name) {
self.socket.emit('login', name);
}
// send chat message
self.chat = function(chat) {
self.socket.emit('chat', chat);
}
self.setResponseListeners = function(socket) {
// handle messages from the server
socket.on('welcome', function(data) {
// request server info
socket.emit('onlineUsers');
self.vent.trigger('loginDone', data);
});
socket.on('loginNameExists', function(data) {
self.vent.trigger('loginNameExists', data);
});
socket.on('loginNameBad', function(data) {
self.vent.trigger('loginNameBad', data);
});
socket.on('onlineUsers', function(data) {
console.log(data);
self.vent.trigger('usersInfo', data);
});
socket.on('userJoined', function(data) {
self.vent.trigger('userJoined', data);
});
socket.on('userLeft', function(data) {
self.vent.trigger('userLeft', data);
});
socket.on('chat', function(data) {
self.vent.trigger('chatReceived', data);
});
}
}
Socket.IO действительно позволяет легко отправлять / получать сообщения между клиентом и сервером. Здесь мы используем два метода:
-
socket.emit(message, [callback])
-
socket.on(message, callback)
callback
Ниже приведена схема, показывающая, как выглядит наш протокол чата:
Главный контроллер
Для заключительной части на стороне клиента, у нас есть наш контроллер, управляющий между представлениями, моделями и клиентом сокетов. Поместите это в /public/js/main.js
var MainController = function() {
var self = this;
// Event Bus for socket client
self.appEventBus = _.extend({}, Backbone.Events);
// Event Bus for Backbone Views
self.viewEventBus = _.extend({}, Backbone.Events);
// initialize function
self.init = function() {
// create a chat client and connect
self.chatClient = new ChatClient({vent: self.appEventBus});
self.chatClient.connect();
// create our views, place login view inside container first.
self.loginModel = new LoginModel();
self.containerModel = new ContainerModel({
viewState: new LoginView({
vent: self.viewEventBus,
model: self.loginModel
})
});
self.containerView = new ContainerView({model: self.containerModel});
self.containerView.render();
};
// View Event Bus Message Handlers
self.viewEventBus.on('login', function(name) {
// socketio login
self.chatClient.login(name);
});
self.viewEventBus.on('chat', function(chat) {
// socketio chat
self.chatClient.chat(chat);
});
// Socket Client Event Bus Message Handlers
// triggered when login success
self.appEventBus.on('loginDone', function() {
self.homeModel = new HomeModel();
self.homeView = new HomeView({vent: self.viewEventBus, model: self.homeModel});
// set viewstate to homeview
self.containerModel.set('viewState', self.homeView);
});
// triggered when login error due to bad name
self.appEventBus.on('loginNameBad', function(name) {
self.loginModel.set('error', 'Invalid Name');
});
// triggered when login error due to already existing name
self.appEventBus.on('loginNameExists', function(name) {
self.loginModel.set('error', 'Name already exists');
});
// triggered when client requests users info
// responds with an array of online users.
self.appEventBus.on('usersInfo', function(data) {
var onlineUsers = self.homeModel.get('onlineUsers');
var users = _.map(data, function(item) {
return new UserModel({name: item});
});
onlineUsers.reset(users);
});
// triggered when a client joins the server
self.appEventBus.on('userJoined', function(username) {
self.homeModel.addUser(username);
self.homeModel.addChat({sender: '', message: username + ' joined room.'});
});
// triggered when a client leaves the server
self.appEventBus.on('userLeft', function(username) {
self.homeModel.removeUser(username);
self.homeModel.addChat({sender: '', message: username + ' left room.'});
});
// triggered when chat receieved
self.appEventBus.on('chatReceived', function(chat) {
self.homeModel.addChat(chat);
});
}
Чтобы загрузить все, мы просто создаем MainController
init
/public/js/main.js
$(document).ready(function() {
var mainController = new MainController();
mainController.init();
});
Вот и все на стороне клиента. Если вы столкнулись с какими-либо ошибками, в Chrome есть отличные средства отладки. Используйте его вкладку сети, чтобы увидеть, действительно ли сообщения обмениваются.
Сторона сервера
Далее мы обратимся к стороне сервера, которая реализована в Node.js, Express и Socket.IO. Поместите этот код, который реализует компонент сервера Express, в /scripts/web.js
// requirements
var express = require('express');
var http = require('http');
var socketio = require('socket.io');
var path = require('path');
// routes
var routes = require('../routes/index.js');
var app = express();
// routes middleware
app.use(app.router);
// serve public folder
app.use(express.static(path.join(__dirname, '../public')));
// serve index.html for every path
app.use(routes.index);
// this is how you use socket io with express
var server = http.createServer(app);
var io = socketio.listen(server);
var port = process.env.PORT || 8080;
server.listen(port, function() {
console.log(' - listening on ' + port+ ' ' + __dirname);
});
// require our chatserver
var ChatServer = require('./chatserver');
// initialize a new chat server.
new ChatServer({io: io}).init();
Чат-сервер
Последняя часть нашего приложения — это чат-сервер. Это отвечает за ведение списка онлайн-пользователей и трансляцию сообщений чата. Первым событием, которое наш сервер получит при новом клиентском соединении, является меткое connection
обработчики событий connection
socket
socket
-
socket.on(message, callback)
callback
message
-
socket.on('disconnect', callback)
callback
-
socket.emit(message, args)
message
-
socket.broadcast.send(message, args)
message
Теперь мы увидели, как обрабатывать сокеты, давайте определим пользовательскую модель внутри /scripts/chatserver.js
// User Model
var User = function(args) {
var self = this;
// Socket field
self.socket = args.socket;
// username field
self.user = args.user;
}
Наконец, наш чат-сервер находится в /scripts/chatserver.js
var Server = function(options) {
var self = this;
self.io = options.io;
// users array
self.users = [];
// initialize function
self.init = function() {
// Fired upon a connection
self.io.on('connection', function(socket) {
self.handleConnection(socket);
});
}
// socket handler for an incoming socket
self.handleConnection = function(socket) {
// wait for a login message
socket.on('login', function(username) {
var nameBad = !username || username.length < 3 || username.length > 10;
// check for badname
if (nameBad) {
socket.emit('loginNameBad', username);
return;
}
var nameExists = _.some(self.users, function(item) {
return item.user == username;
});
// check for already existing name
if (nameExists) {
socket.emit('loginNameExists', username);
} else {
// create a new user model
var newUser = new User({ user: username, socket: socket });
// push to users array
self.users.push(newUser);
// set response listeners for the new user
self.setResponseListeners(newUser);
// send welcome message to user
socket.emit('welcome');
// send user joined message to all users
self.io.sockets.emit('userJoined', newUser.user);
}
});
}
// method to set response listeners
self.setResponseListeners = function(user) {
// triggered when a socket disconnects
user.socket.on('disconnect', function() {
// remove the user and send user left message to all sockets
self.users.splice(self.users.indexOf(user), 1);
self.io.sockets.emit('userLeft', user.user);
});
// triggered when socket requests online users
user.socket.on('onlineUsers', function() {
var users = _.map(self.users, function(item) {
return item.user;
});
user.socket.emit('onlineUsers', users);
});
// triggered when socket send a chat message
user.socket.on('chat', function(chat) {
if (chat) {
self.io.sockets.emit('chat', { sender: user.user, message: chat });
}
});
}
}
Вывод
Мы видели, как создать простое приложение для чата, используя Backbone и Socket.IO. Существует множество функций Socket.IO, которые мы не рассмотрели в этой статье, таких как комнаты и пространства имен. Socket.IO позволяет легко обмениваться сообщениями между клиентами и сервером. Мы использовали Node.js для серверной части, хотя в Socket.IO есть библиотеки для многих других платформ. Для получения дополнительной информации и ресурсов, проверьте вики Socket.IO .
Вы можете найти исходный код этой статьи на GitHub . Живая демоверсия также доступна на Heroku .