Practical Node.js (2018版) 第9章: 使用WebSocket建立实时程序,原生的WebSocket使用介绍,Socket.IO的基本使用介绍。
Real-Time Apps with WebSocket, Socket.IO, and DerbyJS
实时程序的使用变得越来越广泛,如传统的交易,游戏,社交,开发工具DevOps tools, 云服务,新闻。
之所以如此是因为技术的进步。更大的带宽用来传输数据和更多的计算来处理和取回数据。
HTML5的新实时连接标准叫做,WebSocket。
在浏览器,JavaScript给你一个对象WebSocket。这个对象是一个类,它有各类方法用于生成WebSocket protocol client。
WebSocket协议(ws://)不和HTTP或者HTTPS区别非常大。
因此,开发者需要一个特别的ws server。仅仅有一个HTTP server是不够的.(having a HTTP server won't cut it.)
并且你知道,Node.js是一个高效率,非块结构的输入输出平台。
使用Node执行implementing WebSocket servers非常好,因为Node是非常快速的也因为Node是JavaScript, 就像WebSocket clients(比如browser JavaScript)。
因此, Node非常适合当一个后端对儿,和browser和它的WebSocket API。
为了开始你的WebSocket和Node.js, 让我们keep things simple stupid,并学习下面的内容:
- 什么是WebSocket
- Native WebSocket and Node.js with ws module example(原生WebSocket的使用)
- Socket.IO and Express.js example(这章只是简单的介绍了Socket.IO)
- Collaborative online editor example with DerbyJS, Express.js, and MongoDB(未看)
What Is WebSocket?
WebSocket是一个特殊的关联频道channel: 浏览器(客户端)和服务器。
它是一个HTML5协议。
WebSocket的连接是持续的constant。而传统的HTTP请求总是由客户端发起,这意味着服务器有更新时,没法通知客户端(除了Server-side Events)。
WebSocket通过保持客户端和服务器的双向连接,更新就会被及时发出,而无需客户端to poll(通过投票/提问等方式来获得民意,轮询--定时/轮流的询问:客户端向服务器定时发送请求)。
详细的解释见:
http://www.websocket.org/aboutwebsocket.html
在现代浏览器使用WebSocket无需特定的库。
不同server端的API推荐表:
https://stackoverflow.com/questions/1253683/what-browsers-support-html5-websocket-api
其中node,使用Socket.IO(45000✨最高)
再说一句,轮询poll也可以用于web apps的实时响应(这是过去的技巧)。
一些高级库如Socket.IO会在WebSocket不能使用时,回退到轮询的方式。比如出现连接问题或者用户的浏览器版本太低。
轮询相对简单,本章不讲。它可以执行使用一个setInterval()回退函数和一个服务器上的终端。尽管如此,polling不是真正的实时连接,它的每个请求都是separate。
Native WebSocket and Node.js with the ws Module Example
建立一个小的程序,包括建立一个原生WebSocket 执行项目,它在ws模块module的帮助下与Node.js server进行通话。
- Browser WebSocket implementation
- Node.js server with ws module implementation
主要代码在<script>内,我们会从global WebSocket实例化一个对象。
需要提供一个参数:server URL。 ⚠️协议类型改为WebSocket protocol,所以使用ws://
<script type="text/javascript">
var ws = new WebSocket('ws://localhost:3000')
只要连接被建立,我们发送一条信息给服务器
ws.onopen = function(event) {
ws.send('front-end message: ABC')
}
通常,信息被发送以响应用户行为。例如鼠标点击。当我们得到任何从WebSocket location来的信息,这随后的事件处理器被执行:
ws.onmessage = function(event) {
ws.send('server message: ', event.data)
}
另外,添加一个错误事件处理器,记录❌信息:
ws.onerror = function(event) {
console.log('server error message: ', event.data)
}
完全的代码:index.html
<html>
<head>
</head>
<body>
<script type="text/javascript">
var ws = new WebSocket('ws://localhost:3000');
ws.onopen = function(event) {
ws.send('front-end message: ABC');
};
ws.onerror = function(event) {
console.log('server error message: ', event.data);
};
ws.onmessage = function(event) {
console.log('server message: ', event.data);
};
</script>
</body>
</html>
websocket.onopen, websocket.onclose, websocket.onmessage, websocket.onerror,这几个是事件监听器,是函数。每个监听器都会接收一个事件。
Event: 'message'类型是data, 当从服务器收到一条信息后发出。
websocket.send(data, options, callback) 通过连接发送数据。
注意⚠️:https://github.com/websockets/ws#api-docs 上的使用案例的代码有过期的:
//比如:
ws.on('open', function open() {
ws.send('something');
});
//会报告❌ ws.on不是一个函数。
//查看https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback
// on不是一个方法,可以改用addEventListener(type, listener)
Node.js Server with ws Module Implementation
WebSocket.org 提供了echo service用于测试浏览器WebSocket, 但是我们可以建立自己的Node.js server,在ws库的帮助下:
https://github.com/websockets/ws
(http://npmjs.org/ws and https://github.com/einaros/ws)
你可以创建package.json并安装ws。
$ npm init -y
$ npm install ws //建立一个server.js文件。引进ws模块,并初始化服务器进入wss变量。
const WebSocketServer = require('ws').Server
const wss = new WebSocketServer({port: 3000})
类似前端代码,我们使用一个事件模式来等待连接。当这个连接准备好,在回调内我们发送字符串XYZ,并附加一个事件监听on('message')来监听从网页传来的信息。
wss.on('connection', (ws) => {
ws.send('XYZ')
ws.on('message', (message) => {
console.log('received: %s', message)
})
})
进一步,我们加一些逻辑:使用ws.send()和new Date提供当前时间给浏览器:
wss.on('connection', (ws) => {
ws.send('XYZ')
setInterval(()=>{
ws.send((new Date).toLocaleTimeString())
}, 1000)
ws.on('message', (message) => {
console.log('received: %s', message)
})
})
最后在terminal
$node server
$pwd
//print working directory name, 然后在浏览器打开文件index.html
//file:///Users/chen/node_practice/websocket/index.html
因为原生HTML5 webSocket是一个牛逼的技术。因为WebSocket是标准的协议,每个浏览器执行可能不同。另外,通常连接可能丢失并需要再建立!
为处理跨浏览器和后端的兼容,也包括再opening, 大量开发者使用Socket.IO库。
Socket.IO and Express.js Example
完全的Socket.IO库绝对值得一本书来讲。
它是实时,双向的,基于事件的通信。
- Real-time analytics
- 立即的会话和聊天。
- Binary streaming: 可以发送任意blob到前端和后端:image, audio, video.
- Document collaboration: 允许多个用户并发地编辑一个文档并且看其他人的改变。
下面的例子是一个基本的结构, 演示浏览器和服务器之间的频道连接。
最常见的实时web程序的关联,既是一些用户行为的响应,也是从服务器得到更新结果的响应。
本例子,网页渲染一个form。使用Express.js手脚架, Socket.IO,和Pug。
$express -c styl express-socket
$cd express-socket
//查看package.json,添加包:
$ npm install socket.io body-parser ejs
Socket.IO在一些方面可以被看作是另一个server,因为它处理socket connection并且不是标准的HTTP requests。
重构程序文件根目录下的app.js:
https://github.com/azat-co/practicalnode/blob/master/code/ch9/socket-express/app.js#L30
const http = require('http')
const express = require('express')
const path = require('path')
const logger = require('morgan')
const bodyParser = require('body-parser')
const routes = require('./routes/index')
const app = express()
// view engine setup
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'pug')
app.use(logger('dev'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))
app.use(express.static(path.join(__dirname, 'public')))
app.use('/', routes)
const server = http.createServer(app)
const io = require('socket.io').listen(server)
io.sockets.on('connection', (socket) => {
socket.on('messageChange', (data) => {
console.log(data)
socket.emit('receive', data.message.split('').reverse().join(''))
})
})
app.set('port', process.env.PORT || 3000)
server.listen(app.get('port'), () => {
console.log(`Express server listening on port ${app.get('port')}`)
})
然后需要一个前端的template,修改views/index.pug
⚠️这里使用pug, 上面的代码需要修改:
app.set('view engine', 'pug')
extends layout block content
h1= title
p Welcome to
span.received-message #{title}
input(type='text', class='message', placeholder='what is on your mind?', onkeyup='send(this)')
script(src="/socket.io/socket.io.js")
script.
var socket = io.connect('http://localhost:3000');
socket.on('receive', function (message) {
console.log('received %s', message);
document.querySelector('.received-message').innerText = message;
});
var send = function(input) {
console.log(input.value)
var value = input.value;
console.log('sending %s to server', value);
socket.emit('messageChange', {message: value});
}
最后开启服务器node app.js在浏览器和服务器之间建立起来Websocket,实时连接。无需请求和等待。
官网socket.io上的guide的例子:
非常简单,使用html。(还有完全的代码,here)
https://socket.io/get-started/chat/
//新建一个文件夹进入
$ npm init -y
$ npm i express //新建index.js文件
//index.js
var app = require('express')()
var http = require('http').createServer(app)
// var http = require('http').Server(app)
app.get('/', function(req, res) {
res.send('<h1>Hello World!</h1>')
})
http.listen(3000, function() {
console.log('listening on *:3000');
})
- Express初始化一个app对象,然后把它提供给http模块来创建一个http.Server实例
- 定义一个路径‘/’
- 监听端口3000
如果允许node index.js会在terminal看到:
listening on *:3000
打开浏览器:输入localhost:3000显示的是Hello World!
创建一个index.html。
重构route handler,使用sendFile方法:
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
写index.html
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
</body>
</html>
集成Socket.IO
Socket.IO由2部分组成
- A server: 集成Node.js HTTP Server: socket.io
- A client library: 在浏览器加载: socket.io-client
在开发阶段, socket.io自动地服务客户端:
npm i socket.io
然后更新index.js:
var app = require('express')()
var http = require('http').createServer(app)
// var http = require('http').Server(app)
var io = require('socket.io')(http)
app.get('/', function(req, res) {
// res.send('<h1>Hello World!</h1>')
res.sendFile(__dirname + '/index.html')
})
io.on('connection', function(socket) {
console.log('a user connected')
})
http.listen(3000, function() {
console.log('listening on *:3000');
})
通过传递http对象(HTTP server实例),初始化一个socket.io实例。
然后监听connection事件。
现在在index.html,在</body>前面增加代码:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
加载了socket.io-client,并揭露出一个io全局对象,然后连接。
注意⚠️此时,没有给io()指定一个URL, 这样它默认的连接的host。window.location
重新刷新网页,可在terminal上看到连接后输出到控制台的提示信息:a user connected
还可以
const io = require('socket.io-client');
// or with import syntax
import io from 'socket.io-client';
每个socket也可以激活一个断开事件disconnect:
//index.js
io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
console.log('user disconnected');
});
});
如果再刷新几次网页可以看到:
//terminal
a user connected
user disconnected
a user connected
user disconnected
a user connected
Emitting event
Socket.IO背后的主要思想是: 你可以发送和接收任意事件,任意数据。
任意对象可以被编码为JSON, 同时binary data也被支持。binary data
修改index.html:
<script>
var socket = io()
var el = document.querySelector("form")
el.addEventListener("submit", (event) => {
event.preventDefault();
socket.emit('chat message', document.getElementById("m").value)
document.getElementById("m").value = ""
})
</script>
socket发出一个'chat message'信息
修改index.js:
io.on('connection', function(socket){
socket.on('chat message', function(msg){
console.log('message: ' + msg);
});
});
socket接收到'chat message'后,回调函数打印值。
Client
socket.emit(eventName, ...args, callback)
发出一个事件,可以添加任意参数。事件是自定义的string,
Server
socket.on(eventName, callback)
用于接收从浏览器传入的信息。
server.disconnect(close)
close: Boolean, 返回Socket。断开client。
Broadcasting
下一个目标是发送一个事件给服务器上其他的用户。
为了发送一个事件到每个人,Socket.IO给我们了这个方法:
io.emit()
io.emit('some event', { for: 'everyone' });
如果你想要发送一条信息,到所有人,但不包括发送者本身,使用Flag: broadcast
io.on('connection', function(socket){
socket.broadcast.emit('hi');
});
如果是上面的例子,可以这么模拟一下,看看broadcast的作用:
打开两个浏览器窗口localhost:3000,然后修改代码:
index.html增加一个监听事件'hi', 并输出信息到网页的脚本:
socket.on('hi', (msg) => {
console.log(msg)
var x = document.getElementById("messages")
var node = document.createElement('li')
var textnode = document.createTextNode(msg)
node.appendChild(textnode)
x.appendChild(node)
})
index.js:
io.on('connection', function(socket) {
//...增加
Practical Node.js (2018版) 第9章: 使用WebSocket建立实时程序,原生的WebSocket使用介绍,Socket.IO的基本使用介绍。的更多相关文章
- Practical Node.js (2018版) 第7章:Boosting Node.js and Mongoose
参考:博客 https://www.cnblogs.com/chentianwei/p/10268346.html 参考: mongoose官网(https://mongoosejs.com/docs ...
- Practical Node.js (2018版) 第5章:数据库 使用MongoDB和Mongoose,或者node.js的native驱动。
Persistence with MongoDB and Mongoose https://github.com/azat-co/practicalnode/blob/master/chapter5/ ...
- Practical Node.js (2018版) 第10章:Getting Node.js Apps Production Ready
Getting Node.js Apps Production Ready 部署程序需要知道的方面: Environment variables Express.js in production So ...
- Practical Node.js (2018版) 第3章:测试/Mocha.js, Chai.js, Expect.js
TDD and BDD for Node.js with Mocha TDD测试驱动开发.自动测试代码. BDD: behavior-driven development行为驱动开发,基于TDD.一种 ...
- Practical Node.js (2018版) 第8章:Building Node.js REST API Servers
Building Node.js REST API Servers with Express.js and Hapi Modern-day web developers use an architec ...
- Practical Node.js (2018版) 第4章: 模版引擎
Template Engines: Pug and Handlebars 一个模版引擎是一个库或框架.它用一些rules/languages来解释data和渲染views. web app中,view ...
- Practical Node.js (2018版) 13章, Node HTTP/2 Servers
新增的章节. If you are not using HTTP/2, then you are losing out on big improvements. HTTP/2相比http/1有很大的区 ...
- Practical Node.js (2018版) 14章, async code in Node
Asynchronous Code in Node 历史上,Node开发者只能用回调和事件emitters. 现在可以使用一些异步的语法: async module Promises Async/aw ...
- Practical Node.js摘录(2018版)第1,2章。
大神的node书,免费 视频:https://node.university/courses/short-lectures/lectures/3949510 另一本书:全栈JavaScript,学习b ...
随机推荐
- mysql数据库的主从同步,实现读写分离 g
https://blog.csdn.net/qq_15092079/article/details/81672920 前言 1 分别在两台centos 7系统上安装mysql 5.7 2 master ...
- 树莓派3 之 pi3Robot 控制系统配置
需求 个人正在用Python写一个控制系统,技术选型是python3 + Flask + Mysql + Bootstrap.需要将这套系统直接部署到树莓派中. 代码地址:https://github ...
- 记一次mysql事故---纪念逝去的一上午
虚拟机关机后第二天mysql起不来,回想一下我关机前和关机后的操作发现:关机前没关闭mysqld服务就直接init 0了,关机后将虚拟机内存由1G降到724M.笔者保证再也做过别的骚操作了. -- : ...
- spring声明式事务管理方式( 基于tx和aop名字空间的xml配置+@Transactional注解)
1. 声明式事务管理分类 声明式事务管理也有两种常用的方式, 一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解. 显然基于注解的方式更简单易用,更清爽. ...
- 深入浅出--UNIX多进程编程之fork()函数
0前言 上周都在看都在学习unix环境高级编程的第八章--进程控制.也就是这一章中.让我理解了unix中一些进程的原理.以下我就主要依照进程中最重要的三个函数来进行解说.让大家通过阅读这一篇文章彻底明 ...
- [js]js原型链继承小结
这是之前总结的, 发现有很多的毛病,就是重点不突出,重新翻看的时候还是得耗费很长时间去理解这玩意. js中的继承 js中什么是类 1,类是函数数据类型 2.每个类有一个自带prototype属性 pr ...
- 【Java】NO.120.JDK.1.JDK8.1.001-【Java8实战】
Style:Mac Series:Java Since:2018-09-26 End:2018-09-26 Total Hours:1 Degree Of Diffculty:5 Degree Of ...
- linux系统644、755、777权限详解
在linux系统中644.755.777三种权限是非常重要的一些权限了,下面我来详细的介绍644.755.777三种权限的使用,希望对各位有帮助. 常用的linux文件权限:444 r--r--r-- ...
- 从AST编译解析谈到写babel插件
之前一直在掘金上看到一些关于面试写babel插件的文章,最近也在学,以下就是学习后的总结. 关键词:AST编译解析, babel AST编译解析 AST[维基百科]:在计算机科学中,抽象语法树(Abs ...
- FAT32文件系统学习(上)
2011-06-02 22:30:48 目的:需要编写SD读图片的底层驱动程序.所以要了解一个SD卡常用文件系统基本概念.累计学习用时2.5小时. 一,FAT32的保留区 1,引导扇区 :引导扇区是F ...