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 ...
		 
		
	
随机推荐
	
									- 对text字段聚合,没有设置fielddate所以出错
			
http://192.168.60.26:9200/linewell_assets_mgt_es_yh_test/lw_devices/ _mapping { "properties&quo ...
			 
						- 对象何时进入老年代、何时发生full gc
			
一.对象何时进入老年代 (1)当对象首次创建时, 会放在新生代的eden区, 若没有GC的介入,会一直在eden区, GC后,是可能进入survivor区或者年老代 (2)当对象年龄达到一定的大小 , ...
			 
						- Bootstrap modal模态框关闭时,combobox input下拉框仍然保留在页面上
			
问题描述: 当点击模态框的关闭按钮时,下拉框中的内容没有消失,而是移动到了页面左上角 分析:这个问题的定位在于是用的哪种模态框,bootstrap和easyui都可以实现模态框,但是两个方法实现的模态 ...
			 
						- Lint found fatal errors while assembling a release target
			
1.Android 打包错误信息 Generate signed Bundle or APK  打包时,报了一个错,错误信息如下: Error:Execution failed for task ´: ...
			 
						- Storm UI说明
			
一.Storm ui 首页主要分为4块: Cluster Summary,Topology summary,Supervisor summary,Nimbus Configuration Cluste ...
			 
						- 选择排序(Python实现)
			
目录 1. for版本--选择排序 2. while版本--选择排序 3.测试用例 4. 算法时间复杂度分析 1. for版本--选择排序 def select_sort_for(a_list): ' ...
			 
						- Ocelot:API网关概要
			
一.概要 Ocelot是.Net Core下一个开源API网关:Ocelot主要目标是在.NET在微服务或面向服务架构中提供统一的入口服务, Ocelot拿到HttpRequest对象到管道后,先创建 ...
			 
						- 《linux就该这么学》第二节课,安装红帽7,基础命令至2.3小节的笔记
			
笔记 实验环境: 1.安装注意事项:使用VM12版本   安装需要稍后安装系统.   自定义硬件DVD选择镜像位置.   自定义网卡仅主机.   自定义内存:大于等于4G,给予虚拟机2G,大于2G小于 ...
			 
						- Java-番外篇-Java通过代码发给手机发信息
			
一.代码 import java.io.IOException; import org.apache.commons.httpclient.Header; import org.apache.comm ...
			 
						- 给学习立个flag
			
今天是2018年7月7号,此时的砖相比昨天格外烫手,望着手套因被磨破而露出来的半截手指头,一股股热浪溜溜的从指间划过,背后还有小山一样高的砖头,感觉对面today店里的冰镇西瓜又成了不可奢望的梦... ...