Socket.IO - 快速指南
Socket.IO - Overview
Socket.IO是一个用于real-time web applications的JavaScript库。 它支持Web客户端和服务器之间的实时双向通信。 它有两部分:在浏览器中运行的client-side library ,以及node.js的server-side library 。 两个组件都具有相同的API。
Real-time applications
实时应用程序(RTA)是一种在用户感知为即时或当前的时间段内运行的应用程序。
实时应用的一些例子是 -
Instant messengers - Whatsapp,Facebook Messenger等聊天应用程序。您无需刷新您的应用程序/网站即可接收新邮件。
Push Notifications - 当有人在Facebook上的图片中标记您时,您会立即收到通知。
Collaboration Applications - 谷歌文档等Collaboration Applications程序,允许多人同时更新相同的文档,并对所有人的实例应用更改。
Online Gaming - 反恐精英,使命召唤等游戏也是实时应用程序的一些例子。
Why Socket.IO?
使用流行的Web应用程序堆栈(如LAMP(PHP))编写实时应用程序一直非常困难。 它涉及轮询服务器以进行更改,跟踪时间戳,并且它比它应该慢得多。
传统上,套接字是大多数实时系统架构的解决方案,在客户端和服务器之间提供双向通信通道。 这意味着服务器可以将消息推送到客户端。 每当事件发生时,想法是服务器将获取它并将其推送到相关的连接客户端。
Socket.IO非常受欢迎,它被Microsoft Office, Yammer, Zendesk, Trello和许多其他组织用于构建强大的实时系统。 它是GitHub最强大的JavaScript frameworks ,并且最依赖于NPM(节点包管理器)模块。 Socket.IO也有一个庞大的社区,这意味着寻求帮助非常容易。
ExpressJS
我们将使用express来构建Socket.IO将使用的Web服务器。 可以使用任何其他节点 - 服务器端框架甚至节点HTTP服务器。 但是,ExpressJS可以轻松定义路线和其他内容。 要阅读有关express的更多信息并获得有关它的基本知识,请访问ExpressJS教程 。
Socket.IO - Environment
要开始使用Socket.IO进行开发,您需要安装Node和npm (node package manager) 。 如果您没有这些,请转到Node setup以在本地系统上安装节点。 通过在终端中运行以下命令,确认已安装节点和npm。
node --version
npm --version
您应该得到类似于 - 的输出
v5.0.0
3.5.2
打开终端并在终端中输入以下内容以创建新文件夹并输入以下命令 -
$ mkdir test-project
$ cd test-proect
$ npm init
它会问你一些问题; 以下列方式回答他们 -
这将创建一个'package.json node.js'配置文件。 现在我们需要安装Express和Socket.IO 。 要安装它们并将它们保存到package.json文件,请在终端中将以下命令输入到项目目录中。
npm install --save express socket.io
最后一件事是我们应该继续重启服务器。 当我们进行更改时,我们需要一个名为nodemon的工具。 要安装nodemon ,请打开终端并输入以下命令 -
npm install -g nodemon
每当您需要启动服务器时,而不是使用node app.js使用nodemon app.js 这将确保您无需在更改文件时重新启动服务器。 它加快了开发过程。
现在,我们已经建立了开发环境。 现在让我们开始使用Socket.IO开发实时应用程序。
Socket.IO - Hello World
创建一个名为app.js的文件并输入以下代码来设置快速应用程序 -
var app = require('express')();
var http = require('http').Server(app);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
http.listen(3000, function() {
console.log('listening on *:3000');
});
我们需要一个index.html文件来提供服务,创建一个名为index.html的新文件并在其中输入以下代码 -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<body>Hello world</body>
</html>
要测试这是否有效,请转到终端并使用以下命令运行此应用程序 -
nodemon app.js
这将在localhost:3000上运行服务器。 转到浏览器并输入localhost:3000进行检查。
这将设置我们的快速应用程序,现在在根路由上提供HTML文件。 现在,每当有人导航/关闭此页面时,每当用户访问此页面并且“用户断开连接”时,我们将需要Socket.IO并记录“用户已连接”。
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
//Whenever someone connects this gets executed
io.on('connection', function(socket) {
console.log('A user connected');
//Whenever someone disconnects this piece of code executed
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
http.listen(3000, function() {
console.log('listening on *:3000');
});
require('socket.io')(http)创建一个连接到http服务器的新socket.io实例。 io.on event handler使用套接字对象处理io.on event handler连接,断开连接等。
我们已经设置了服务器来记录连接和断开连接的消息。 我们现在必须包含客户端脚本并在那里初始化套接字对象,以便客户端可以在需要时建立连接。 该脚本由我们的io server在'/socket.io/socket.io.js' 。
完成上述过程后,index.html文件将如下所示 -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
<body>Hello world</body>
</html>
如果您现在转到localhost:3000(确保您的服务器正在运行),您将在浏览器中打印Hello World 。 现在检查您的服务器控制台日志,它将显示以下消息 -
A user connected
如果刷新浏览器,它将断开套接字连接并重新创建。 您可以在控制台日志中看到以下内容 -
A user connected
A user disconnected
A user connected
我们现在有套接字连接工作。 这是在Socket.IO中设置连接的容易程度。
Socket.IO - Event Handling
套接字基于事件工作。 有一些保留事件,可以使用服务器端的套接字对象访问。
这些是 -
- Connect
- Message
- Disconnect
- Reconnect
- Ping
- Join and
- Leave
客户端套接字对象还为我们提供了一些保留事件,它们是 -
- Connect
- Connect_error
- Connect_timeout
- Reconnect, etc
在Hello World示例中,我们使用连接和断开连接事件来记录用户连接和离开时的情况。 现在我们将使用message事件将消息从服务器传递到客户端。 为此,请修改io.on ('connection',function(socket))调用以包含以下内容 -
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
io.on('connection', function(socket) {
console.log('A user connected');
//Send a message after a timeout of 4seconds
setTimeout(function() {
socket.send('Sent a message 4seconds after connection!');
}, 4000);
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
http.listen(3000, function() {
console.log('listening on *:3000');
});
这将在客户端连接四秒后向客户端发送一个名为message(built in)的事件。 套接字对象上的send函数关联'message'事件。
现在,我们需要在客户端处理此事件。 因此,编辑index.html脚本标记以包含以下代码 -
<script>
var socket = io();
socket.on('message', function(data){document.write(data)});
</script>
我们现在正在处理客户端上的“消息”事件。 当您现在在浏览器中转到该页面时,您将看到以下屏幕截图。
传递4秒后,服务器发送消息事件,我们的客户端将处理它并产生以下输出 -
Note - 我们在这里发送了一串文字; 我们也可以在任何情况下发送一个对象。
Message是由API提供的内置事件,但在实际应用程序中没有太大用处,因为我们需要能够区分事件。
为此,Socket.IO为我们提供了创建custom events的能力。 您可以使用socket.emit函数创建和触发自定义事件。
例如,以下代码发出一个名为testerEvent的事件 -
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
io.on('connection', function(socket) {
console.log('A user connected');
//Send a message when
setTimeout(function() {
//Sending an object when emmiting an event
socket.emit('testerEvent', { description: 'A custom event named testerEvent!'});
}, 4000);
socket.on('disconnect', function () {
console.log('A user disconnected');
});
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
要在客户端上处理此自定义事件,我们需要一个侦听事件testerEvent的侦听器。 以下代码在客户端上处理此事件 -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('testerEvent', function(data){document.write(data.description)});
</script>
<body>Hello world</body>
</html>
这将以与前一个示例相同的方式工作,在这种情况下事件为testerEvent。 当您打开浏览器并转到localhost:3000时,您将受到欢迎 -
Hello world
四秒钟后,此事件将被触发,浏览器将文本更改为 -
A custom event named testerEvent!
我们还可以从客户端发出事件。 要从客户端发出事件,请使用套接字对象上的emit函数。
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.emit('clientEvent', 'Sent an event from the client!');
</script>
<body>Hello world</body>
</html>
要处理这些事件,请使用服务器上套接字对象的on function 。
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
io.on('connection', function(socket) {
socket.on('clientEvent', function(data) {
console.log(data);
});
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
所以,现在如果我们转到localhost:3000,我们将获得一个名为clientEvent的自定义事件。 此事件将通过记录在服务器上处理 -
Sent an event from the client!
Socket.IO - Broadcasting
广播意味着向所有连接的客户端发送消息。 广播可以在多个级别完成。 我们可以将消息发送到所有连接的客户端,命名空间上的客户端和特定房间中的客户端。 要向所有客户端广播事件,我们可以使用io.sockets.emit方法。
Note - 这将向ALL连接的客户端发出事件(事件可能触发了此事件的套接字)。
在此示例中,我们将向所有用户广播已连接客户端的数量。 更新app.js文件以包含以下内容。
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
var clients = 0;
io.on('connection', function(socket) {
clients++;
io.sockets.emit('broadcast',{ description: clients + ' clients connected!'});
socket.on('disconnect', function () {
clients--;
io.sockets.emit('broadcast',{ description: clients + ' clients connected!'});
});
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
在客户端,我们只需要处理广播事件 -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('broadcast',function(data) {
document.body.innerHTML = '';
document.write(data.description);
});
</script>
<body>Hello world</body>
</html>
如果您连接四个客户端,您将获得以下结果 -
这是向每个人发送一个事件。 现在,如果我们想要向每个人发送一个事件,但是导致它的客户端(在前面的示例中,它是由连接时的新客户端引起的),我们可以使用socket.broadcast.emit 。
让我们向新用户发送欢迎消息,并向其他客户更新他/她的加入。 因此,在您的app.js文件中,在客户端连接上向他发送欢迎消息并将连接的客户端号码广播给所有其他人。
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
var clients = 0;
io.on('connection', function(socket) {
clients++;
socket.emit('newclientconnect',{ description: 'Hey, welcome!'});
socket.broadcast.emit('newclientconnect',{ description: clients + ' clients connected!'})
socket.on('disconnect', function () {
clients--;
socket.broadcast.emit('newclientconnect',{ description: clients + ' clients connected!'})
});
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
你的html来处理这个事件 -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('newclientconnect',function(data) {
document.body.innerHTML = '';
document.write(data.description);
});
</script>
<body>Hello world</body>
</html>
现在,最新的客户端收到欢迎消息,其他客户端获取当前连接到服务器的客户端数量。
Socket.IO - Namespaces
Socket.IO允许您“命名”套接字,这实际上意味着分配不同的端点或路径。 这是一个有用的功能,可以最大限度地减少资源数量(TCP连接),同时通过在通信通道之间引入分离来分离应用程序中的问题。 多个名称空间实际上共享相同的WebSockets连接,从而在服务器上节省了我们的套接字端口。
命名空间是在服务器端创建的。 但是,客户端通过向服务器发送请求来加入它们。
默认命名空间
根命名空间“/”是默认命名空间,如果客户端在连接到服务器时未指定命名空间,则由命名空间连接。 使用套接字对象客户端连接到服务器的所有连接都是默认命名空间。 例如 -
var socket = io();
这会将客户端连接到默认命名空间。 此命名空间连接上的所有事件都将由服务器上的io object处理。 前面的所有示例都使用默认命名空间与服务器进行通信并返回。
自定义命名空间
我们可以创建自己的自定义命名空间。 要设置自定义命名空间,我们可以在服务器端调用'of'函数 -
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
var nsp = io.of('/my-namespace');
nsp.on('connection', function(socket) {
console.log('someone connected');
nsp.emit('hi', 'Hello everyone!');
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
现在,要将客户端连接到此命名空间,您需要提供命名空间作为io constructor call的参数,以在客户端创建连接和套接字对象。
例如,要连接到上面的命名空间,请使用以下HTML -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io('/my-namespace');
socket.on('hi',function(data) {
document.body.innerHTML = '';
document.write(data);
});
</script>
<body></body>
</html>
每当有人连接到此命名空间时,他们都会收到“hi”事件。
Socket.IO - Rooms
在每个命名空间内,您还可以定义套接字可以加入和离开的任意通道。 这些频道称为房间。 客房用于进一步分离问题。 房间也共享相同的套接字连接,如命名空间。 使用房间时要记住的一件事是它们只能在服务器端连接。
加入房间
您可以在套接字上调用join方法以将套接字订阅到给定的通道/房间。 例如,让我们创建一个名为'room-《room-number》'并加入一些客户。 一旦这个房间满了,创建另一个房间并加入那里的客户。
Note - 我们目前在默认命名空间中执行此操作,即“/”。 您也可以以相同的方式在自定义命名空间中实现此功能。
要加入房间,您需要提供房间名称作为加入函数调用的参数。
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
var roomno = 1;
io.on('connection', function(socket) {
//Increase roomno 2 clients are present in a room.
if(io.nsps['/'].adapter.rooms["room-"+roomno] && io.nsps['/'].adapter.rooms["room-"+roomno].length > 1) roomno++;
socket.join("room-"+roomno);
//Send this event to everyone in the room.
io.sockets.in("room-"+roomno).emit('connectToRoom', "You are in room no. "+roomno);
})
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
只需在客户端上处理此connectToRoom事件即可。
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('connectToRoom',function(data) {
document.body.innerHTML = '';
document.write(data);
});
</script>
<body></body>
</html>
现在,如果您连接三个客户端,前两个将获得以下消息 -
You are in room no. 1
最后一个客户端将收到以下消息 -
You are in room no. 2
离开一个房间
要离开房间,您需要像调用套接字上的join函数一样调用leave函数。
例如 - 要离开房间'room-1' ,
socket.leave("room-"+roomno);
Socket.IO - Error Handling
到目前为止,我们一直致力于本地服务器,这几乎不会给我们带来与连接,超时等相关的错误。但是,在现实生产环境中,处理此类错误至关重要。 因此,我们现在将讨论如何在客户端处理连接错误。
客户端API为我们提供了以下内置事件 -
Connect - 客户端成功连接时。
Connecting - 当客户端正在连接时。
Disconnect - Disconnect客户端连接时。
Connect_failed - 与服务器的连接失败。
Error - 从服务器发送错误事件。
Message - 服务器使用send函数发送消息时。
Reconnect - 重新连接到服务器成功。
Reconnecting - 当客户Reconnecting在连接时。
Reconnect_failed - 重新连接尝试失败时。
为了处理错误,我们可以使用我们在客户端上创建的out-socket对象来处理这些事件。
例如 - 如果我们的连接失败,我们可以使用以下代码再次连接到服务器 -
socket.on('connect_failed', function() {
document.write("Sorry, there seems to be an issue with the connection!");
})
Socket.IO - Logging and Debugging
Socket.IO使用由ExpresJS的主要作者开发的非常着名的调试模块,称为debug 。 早期的Socket.IO用于将所有内容记录到控制台,这使得调试问题变得非常困难。 在v1.0发行版之后,您可以指定要记录的内容。
Server-side
查看可用信息的最佳方法是使用* -
DEBUG=* node app.js
这将着色并输出服务器控制台发生的所有事情。 例如,我们可以考虑以下屏幕截图。
Client-side
将其粘贴到控制台,单击“输入”并刷新页面。 这将再次将与Socket.io相关的所有内容输出到您的控制台。
localStorage.debug = '*';
您可以使用以下命令限制输出以获取来自套接字的传入数据的调试信息。
localStorage.debug = 'socket.io-client:socket';
如果您使用第二个语句记录信息,您可以看到如下截图的结果 -
这里有一篇关于socket.io调试的非常好的博客文章here.
Socket.IO - Internals
在本章中,我们将讨论使用Socket.IO,事件和消息进行回退,连接。
Fallbacks
Socket.IO有很多底层传输机制,它处理由于跨浏览器问题,WebSocket实现,防火墙,端口阻塞等引起的各种约束。
尽管W3C已经为WebSocket API定义了规范,但它仍然缺乏实现。 Socket.IO为我们提供了回退机制,可以处理这些问题。 如果我们使用本机API开发应用程序,我们必须自己实现回退。 Socket.IO按以下顺序包含大量回退 -
- WebSockets
- FlashSocket
- XHR长轮询
- XHR多部分流媒体
- XHR民意调查
- JSONP轮询
- iframes
使用Socket.IO连接
Socket.IO连接以握手开始。 这使握手成为协议的一个特殊部分。 除握手外,协议中的所有其他事件和消息都通过套接字传输。
Socket.IO旨在与Web应用程序一起使用,因此假设这些应用程序始终能够使用HTTP。 正是由于这种推理,Socket.IO握手通过握手URI上的POST请求(传递给connect方法)在HTTP上进行。
事件和消息
WebSocket本机API仅发送消息。 Socket.IO为这些消息提供了一个附加层,允许我们创建事件,并通过分离发送的不同类型的消息再次帮助我们轻松地开发应用程序。
本机API仅以纯文本形式发送消息。 这也由Socket.IO负责。 它为我们处理数据的序列化和反序列化。
我们有一个官方客户端API用于网络。 对于其他客户端,如本机移动电话,其他应用程序客户端,我们也可以使用Socket.IO使用以下步骤。
Step 1 - 需要使用上面讨论的相同连接协议建立连接。
Step 2 - 消息需要与Socket.IO指定的格式相同。 此格式使Socket.IO能够确定消息的类型以及消息中发送的数据以及一些对操作有用的元数据。
邮件格式为 -
[type] : [id ('+')] : [endpoint] (: [data]
上述命令中的参数说明如下 -
Type是单个数字整数,指定它是什么类型的消息。
ID是消息ID,用于确认的增量整数。
Endpoint是要将消息传递到的套接字端点...
Data是要传递到套接字的关联数据。 对于消息,它被视为纯文本,对于其他事件,它被视为JSON。
在下一章中,我们将在Socket.IO中编写一个聊天应用程序。
Socket.IO - Chat Application
现在我们已经熟悉了Socket.IO,让我们编写一个聊天应用程序,我们可以用它在不同的聊天室聊天。 我们将允许用户选择用户名并允许他们使用它们进行聊天。 首先,让我们设置我们的HTML文件来请求用户名 -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
<body>
<input type = "text" name = "name" value = "" placeholder = "Enter your name!">
<button type = "button" name = "button">Let me chat!</button>
</body>
</html>
现在我们已经设置了HTML来请求用户名,让我们创建服务器以接受来自客户端的连接。 我们将允许人们使用setUsername事件发送他们选择的用户名。 如果用户存在,我们将通过userExists事件响应,否则使用userSet事件。
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
users = [];
io.on('connection', function(socket) {
console.log('A user connected');
socket.on('setUsername', function(data) {
if(users.indexOf(data) > -1) {
users.push(data);
socket.emit('userSet', {username: data});
} else {
socket.emit('userExists', data + ' username is taken! Try some other username.');
}
})
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
当人们点击按钮时,我们需要将用户名发送到服务器。 如果用户存在,我们会显示错误消息; 否则,我们会显示一个消息屏幕 -
<!DOCTYPE html>
<html>
<head>
<title>Hello world</title>
</head>
<script src = "/socket.io/socket.io.js"></script>
<script>
var socket = io();
function setUsername() {
socket.emit('setUsername', document.getElementById('name').value);
};
var user;
socket.on('userExists', function(data) {
document.getElementById('error-container').innerHTML = data;
});
socket.on('userSet', function(data) {
user = data.username;
document.body.innerHTML = '<input type = "text" id = "message">\
<button type = "button" name = "button" onclick = "sendMessage()">Send</button>\
<div id = "message-container"></div>';
});
function sendMessage() {
var msg = document.getElementById('message').value;
if(msg) {
socket.emit('msg', {message: msg, user: user});
}
}
socket.on('newmsg', function(data) {
if(user) {
document.getElementById('message-container').innerHTML += '<div><b>' +
data.user + '</b>: ' + data.message + '</div>'
}
})
</script>
<body>
<div id = "error-container"></div>
<input id = "name" type = "text" name = "name" value = ""
placeholder = "Enter your name!">
<button type = "button" name = "button" onclick = "setUsername()">
Let me chat!
</button>
</body>
</html>
现在,如果您使用相同的用户名连接两个客户端,它将给您一个错误,如下面的屏幕截图所示 -
一旦您提供了可接受的用户名,您将进入一个带有消息框和按钮的屏幕以发送消息。 现在,我们必须处理并将消息定向到连接的客户端。 为此,修改您的app.js文件以包含以下更改 -
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res) {
res.sendfile('index.html');
});
users = [];
io.on('connection', function(socket) {
console.log('A user connected');
socket.on('setUsername', function(data) {
console.log(data);
if(users.indexOf(data) > -1) {
socket.emit('userExists', data + ' username is taken! Try some other username.');
} else {
users.push(data);
socket.emit('userSet', {username: data});
}
});
socket.on('msg', function(data) {
//Send message to everyone
io.sockets.emit('newmsg', data);
})
});
http.listen(3000, function() {
console.log('listening on localhost:3000');
});
现在将任意数量的客户端连接到您的服务器,为他们提供用户名并开始聊天! 在下面的示例中,我们连接了两个名为Ayush和Harshit的客户端,并从两个客户端发送了一些消息 -