Node 是一个面向网络而生的平台,它具有事件驱动、无阻塞、单线程等特性,具备良好的可伸缩性,使得它十分轻量,适合在分布式网络中扮演各种各样的角色。同时 Node 提供的 API 十分贴合网络,适合用它基础的 API 构建灵活的网络服务。本课程的内容就是给大家介绍 Node 在网络通信编程方面的具体能力。
利用 Node 可以十分方便的搭建网络服务器。在 Web 领域,大多数的编程语言需要专门的 Web 服务器作为容器,如 ASP、ASP.NET 需要 IIS 作为服务器,PHP 需要打在 Apache 或 Nginx 环境等,JSP 需要 Tomcat 服务器等。但对于 Node 而言,只需要几行代码即可构建服务器,无需额外的容器。
Node 提供了 net、dgram、http、https 这4个模块,分别用于处理 TCP、UDP、HTTP、HTTPS,适用于服务器端和客户端。
我们每天使用互联网,你是否想过,它是如何实现的?
全世界几十亿台电脑,连接在一起,两两通信。上海的某一块网卡送出信号,洛杉矶的另一块网卡居然就收到了,两者实际上根本不知道对方的物理位置,你不觉得这是很神奇的事情吗?
互联网的核心是一系列协议,总称为"互联网协议"(Internet Protocol Suite)。它们对电脑如何连接和组网,做出了详尽的规定。理解了这些协议,就理解了互联网的原理。
互联网的实现,分成好几层。每一层都有自己的功能,就像建筑物一样,每一层都靠下一层支持。
用户接触到的,只是最上面的一层,根本没有感觉到下面的层。要理解互联网,必须从最下层开始,自下而上理解每一层的功能。
如何分层有不同的模型,有的模型分七层
为了好理解,有的模型分五层
越下面的层,越靠近硬件;越上面的层,越靠近用户。
ping 127.0.0.1 能ping通即代表电脑网卡没有问题,网络正常就能上网
计算机在网络中的标识号码,好比每个人的电话号码。
ip地址的作用: 通过ip地址在网络中找到对应的设备,然后可以给这个设备发送数据
ip地址类型分为:ipv4、ipv6
端口号分为知名端口号和动态端口号(知名端口号是系统使用的,动态端口号是程序员设置使用的)
知名端口号:范围从 0-1023
动态端口范围:1024-65535
,当程序关闭时,同时也就释放了所占用的端口号
查看端口号:netstat -an
查看端口号被哪个程序占用: lsof -i[tcp/udp]:端口号
(找不到时,使用管理员权限,加sudo)
根据进程编号杀死指定进程:kill -9 进程号
方便记忆某台电脑的主机地址,域名能解析出来一个ip地址(DNS解析)
TCP和UDP: 都是数据传输方式的协议.比如说我要给你钱, 我是以手把手的方式拿给你呢还是以快递的方式寄给你呢.
TCP(传输控制协议)
UDP(用户数据报协议)
概念:英文全拼(User Datagram Protocol)简称用户数据报协议,它是无连接的、不可靠的网络传输协议
核心特点:无连接、资源开销小、传输速度快、UDP每个数据包最大是64K
优点:不需要连接,传输速度快,资源开销小
缺点:传输数据不可靠,容易丢失数据包,没有流量控制,当对方没有及时接收数据,发送方一直发送数据会导致缓冲区数据满了,电脑出现卡死情况,所以接收方需要及时接收数据
什么时候用 TCP,什么时候用 UDP?
Socket应用于两个不同客户端之间的通信及数据传输.中文名字叫套接字.
编程源于生活. 打个活生生的例子来说, 汽车和加油机.我们如果想把加油机里面的油输到汽车上, 那么汽车这边需要有一个端口, 加油机这边也要有一个端口, 两边端口各加一个套接头套着(好比adaptor),然后中间连上管道来输油. 我认为Socket的角色就是这个套接头.
简单来说, 要想在两个客户端之间传数据, 那么两个客户端各自都要有一个Socket.
TCP 服务在网络应用中十分常见,目前大多数的应用都是基于TCP搭建而成的。
TCP 全名为传输控制协议,在 OSI 模型(由七层模型,分别为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层)中属于传输层协议。许多应用层协议基于TCP构建,典型的是HTTP、SMTP、IMAP等协议。
七层协议示意图如下:
层级 | 作用 |
---|---|
应用层 | HTTP、SMTP、IMAP等 |
表示层 | 加密/解密等 |
会话层 | 通信连接/维持会话 |
传输层 | TCP/UDP |
网络层 | IP |
链路层 | 网络特有的链路接口 |
物理层 | 网络物理硬件 |
TCP 是面向连接的协议,其显著的特征是在传输之前需要3次握手形成会话,如下图所示
只有会话形成之后,服务器端和客户端之间才能互相发送数据。在创建会话的过程中,服务器端和客户端分别提供一个套接字,这个两个套接字共同形成一个连接。服务器端与客户端则通过套接字实现两者之间连接通信的操作。下面是一个基于 Socket 套接字编程的网络通信模型。
服务端:
const net = require('net');
const server = net.createServer((c) => {
// 'connection' listener
console.log('client connected');
c.on('end', () => {
console.log('client disconnected');
});
c.write('hello\r\n');
c.pipe(c);
});
server.on('error', (err) => {
throw err;
});
server.listen(8124, () => {
console.log('server bound');
});
客户端:
const net = require('net');
const client = net.createConnection({ port: 8124 }, () => {
// 'connect' listener
console.log('connected to server!');
client.write('world!\r\n');
});
client.on('data', (data) => {
console.log(data.toString());
client.end();
});
client.on('end', () => {
console.log('disconnected from server');
});
官方API文档:https://nodejs.org/dist/latest-v10.x/docs/api/net.html
{ port: 12346, family: ‘IPv4‘, address: ‘127.0.0.1‘ }
信息server.getConnections()
替换server.ref()
恢复服务器侦听write()
发送数据时,另一端会触发 data
事件,事件传递的数据即是 write()
发送的数据{ port: 12346, family: ‘IPv4‘, address: ‘127.0.0.1‘ }
net.createConnection()
作用相等
new net.Server()
用户登录
客户端:
{
"type": "login",
"nickname": "xxx"
}
服务端:
{
"type": "login",
"success": true | false,
"message": "登录成功|失败",
"sumUsers": 10
}
广播消息
客户端:
{
"type": "broadcast",
"message": "xxx"
}
服务端:
{
"type": "broadcast",
"nickname": "xxx",
"message": "xxx"
}
点对点消息
客户端:
{
"type": "p2p",
"to": "xxx",
"message": "xxx"
}
服务端:
{
"type": "p2p",
"from": "xxx",
"to": "xxx",
"message": "xxx"
}
上线|离线通知日志
服务端:
{
"type": "log",
"message": "xxx 进入|离开了聊天室,当前在线人数:xx"
}
{
"type": "xxx"
}
内容安排:
User Datagram Protocol,简称 UDP ,又称用户数据报协议
和 TCP 一样,位于网络传输层用于处理数据包
UDP 最大的特点是无连接
UDP 传输速度快
支持一对一通信,也支持一对多通信
TCP和UDP: 都是数据传输方式的协议.比如说我要给你钱, 我是以手把手的方式拿给你呢还是以快递的方式寄给你呢.
UDP | TCP | |
---|---|---|
连接 | 无连接 | 面向连接 |
速度 | 无需建立连接,速度较快 | 需要建立连接,速度较慢 |
目的主机 | 一对一,一对多 | 仅能一对一 |
带宽 | UDP 报头较短,消耗带宽更少 | 消耗更多的带宽 |
消息边界 | 有 | 无 |
可靠性 | 低 | 高 |
顺序 | 无序 | 有序 |
注:事实上,UDP协议的这种乱序性基本上很少出现,通常只会在网络非常拥挤的情况下才有可能发生。
什么时候用 TCP,什么时候用 UDP?
1、UDP 单播
0.0.0.0 ~ 223.255.255.255
2、UDP 广播
目的地址为网络中的所有设备
255.255.255.255
192.168.10.255
3、UDP 组播
多播(Multicast)也叫组播,把信息传递给一组目的地地址
地址范围:224.0.0.0 ~ 239.255.255.255
224.0.0.0 ~ 224.0.0.255
为永久组地址,224.0.0.0.0
保留不分配,其它供路由协议使用
224.0.1.0 ~ 224.0.1.255
为公用组播地址,可以用于 Internet
224.0.2.0 ~ 238.255.255.255
为用户可用的组播地址(临时组),全网范围有效,使用需要申请
239.0.0.0 ~ 239.255.255.255
为本地管理组播地址,仅在特定本地范围有效
单播传输(Unicast):在发送者和每一接收者之间实现点对点网络连接。如果一台发送者同时给多个的接收者传输相同的数据,也必须相应的复制多份的相同数据包。如果有大量主机希望获得数据包的同一份拷贝时,将导致发送者负担沉重、延迟长、网络拥塞;为保证一定的服务质量需增加硬件和带宽。
广播(Broadcast):是指在IP子网内广播数据包,所有在子网内部的主机都将收到这些数据包。广播意味着网络向子网每一个主机都投递一份数据包,不论这些主机是否乐于接收该数据包。所以广播的使用范围非常小,只在本地子网内有效,通过路由器和网络设备控制广播传输。在网络中的应用较多,如客户机通过DHCP自动获得IP地址的过程就是通过广播来实现的。但是与单播和多播相比,广播几乎占用了子网内网络的所有带宽
组播:组播解决了单播和广播方式效率低的问题。当网络中的某些用户需求特定信息时,组播源(即组播信息发送者)仅发送一次信息,组播路由器借助组播路由协议为组播数据包建立树型路由,被传递的信息在尽可能远的分叉路口才开始复制和分发。网上视频会议、网上视频点播特别适合采用多播方式。
1、单播面对 "一对多"
在单播通信中每一个数据包都有确切的目的IP地址
对于同一份数据,如果存在多个接收者,Server 需发送与接收者数目相同的单播数据包
当接收者成百上千时,将极大的加重 Server 的负担
2、广播面对 "一对多"
广播数据包被限制在局域网中
一旦有设备发送广播数据则广播域内所有设备都收到这个数据包,并且不得不消耗资源去处理,大量的广播数据包将消耗网络的带宽及设备资源
在 IPv6 中,广播的报文传输方式被取消
3、组播面对 "一对多"
组播非常适合一对多的模型,只有加入到特定组播组的成员,才会接收到组播数据。当存在多个组播组成员时,源无需发送多个数据拷贝,仅需发送一份即可,组播网络设备会根据实际需要转发或拷贝组播数据
数据流只发送给加入该组播组的接收者(组成员),而不需要该数据的设备不会收到该组播流量
相同的组播报文,在一段链路上仅有一份数据,大大提高了网络资源的利用率
Node 为我们提供了 dgram 模块用于构建 UDP 服务。
使用该模块创建 UDP 套接字非常简单,UDP 套接字一旦创建,既可以作为客户端发送数据,也可以作为服务器接收数据。
const dgram = require('dgram')
const socket = dgram.createSocket('udp4')
API | 说明 |
---|---|
bind() | 绑定端口和主机 |
address() | 返回 Socket 地址对象 |
close() | 关闭 Socket 并停止监听 |
send() | 发送消息 |
addMembership() | 添加组播成员 |
dropMembership() | 删除组播成员 |
setBroadcast() | 设置是否启动广播 |
setTTL() | 设置数据报生存时间 |
setMulticastTTL() | 设置组播数据报生存时间 |
API | 说明 |
---|---|
listening | 监听成功时触发,仅触发一次 |
message | 收到消息时触发 |
error | 发生错误时触发 |
close | 关闭 Socket 时触发 |
const dgram = require('dgram')
const server = dgram.createSocket('udp4')
server.on('listening', () => {
const address = server.address()
console.log(`server running ${address.address}:${address.port}`)
})
server.on('message', (msg, remoteInfo) => {
console.log(`server got ${msg} from ${remoteInfo.address}:${remoteInfo.port}`)
server.send('world', remoteInfo.port, remoteInfo.address)
})
server.on('error', err => {
console.log('server error', err)
})
server.bind(3000)
const dgram = require('dgram')
const client = dgram.createSocket('udp4')
// client.send('hello', 3000, 'localhost')
client.on('listening', () => {
const address = client.address()
console.log(`client running ${address.address}:${address.port}`)
client.send('hello', 3000, 'localhost')
})
client.on('message', (msg, remoteInfo) => {
console.log(`client got ${msg} from ${remoteInfo.address}:${remoteInfo.port}`)
})
client.on('error', err => {
console.log('client error', err)
})
client.bind(8000)
const dgram = require('dgram')
const server = dgram.createSocket('udp4')
server.on('listening', () => {
const address = server.address()
console.log(`server running ${address.address}:${address.port}`)
server.setBroadcast(true) // 开启广播模式
server.send('hello', 8000, '255.255.255.255')
// 每隔2秒发送一条广播消息
setInterval(function () {
// 直接地址 192.168.10.255
// 受限地址 255.255.255.255
server.send('hello', 8000, '192.168.10.255')
// server.send('hello', 8000, '255.255.255.255')
}, 2000)
})
server.on('message', (msg, remoteInfo) => {
console.log(`server got ${msg} from ${remoteInfo.address}:${remoteInfo.port}`)
server.send('world', remoteInfo.port, remoteInfo.address)
})
server.on('error', err => {
console.log('server error', err)
})
server.bind(3000)
const dgram = require('dgram')
const client = dgram.createSocket('udp4')
client.on('message', (msg, remoteInfo) => {
console.log(`client got ${msg} from ${remoteInfo.address}:${remoteInfo.port}`)
})
client.on('error', err => {
console.log('client error', err)
})
client.bind(8000)
const dgram = require('dgram')
const server = dgram.createSocket('udp4')
server.on('listening', () => {
const address = server.address()
setInterval(function () {
server.send('hello', 8000, '224.0.1.100')
}, 2000)
})
server.on('message', (msg, remoteInfo) => {
console.log(`server got ${msg} from ${remoteInfo.address}:${remoteInfo.port}`)
server.send('world', remoteInfo.port, remoteInfo.address)
})
server.on('error', err => {
console.log('server error', err)
})
server.bind(3000)
const dgram = require('dgram')
const client = dgram.createSocket('udp4')
client.on('listening', () => {
const address = client.address()
console.log(`client running ${address.address}:${address.port}`)
client.addMembership('224.0.1.100')
})
client.on('message', (msg, remoteInfo) => {
console.log(`client got ${msg} from ${remoteInfo.address}:${remoteInfo.port}`)
})
client.on('error', err => {
console.log('client error', err)
})
client.bind(8000)
内容安排:
TCP 和 UDP 都属于网络传输层协议,如果要构建高效的网络应用,就应该从传输层进行着手。但是对于经典的浏览器网页和服务端通信场景,如果单纯的使用更底层的传输层协议则会变得麻烦。
所以对于经典的B(Browser)S(Server)通信,基于传输层之上专门制定了更上一层的通信协议:HTTP,用于浏览器和服务端进行通信。由于 HTTP 协议本身并不考虑数据如何传输及其他细节问题,所以属于应用层协议。
Node 提供了基本的 http 和 https 模块用于 HTTP 和 HTTPS 的封装。
const http = require('http')
const server = http.createServer()
API | 说明 |
---|---|
Event:‘close‘ | 服务关闭时触发 |
Event:‘request‘ | 收到请求消息时触发 |
server.close() | 关闭服务 |
server.listening | 获取服务状态 |
API | 说明 |
---|---|
request.method | 请求方法 |
request.url | 请求路径 |
request.headers | 请求头 |
request.httpVersion | 请求HTTP协议版本 |
API | 说明 |
---|---|
response.end() | 结束响应 |
response.setHeader(name, value) | 设置响应头 |
response.removeHeader(name, value) | 删除响应头 |
response.statusCode | 设置响应状态码 |
response.statusMessage | 设置响应状态短语 |
response.write() | 写入响应数据 |
response.writeHead() | 写入响应头 |
const http = require('http')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('Hello World\n')
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})
const http = require('http')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
const url = req.url
if (url === '/') {
res.end('Hello World!')
} else if (url === '/a') {
res.end('Hello a!')
} else if (url === '/b') {
res.end('Hello b!')
} else {
res.statusCode = 404
res.end('404 Not Found.')
}
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})
const http = require('http')
const fs = require('fs')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
fs.readFile('./index.html', (err, data) => {
if (err) {
throw err
}
res.statusCode = 200
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(data)
})
// res.end(`
// <h1>Hello World!</h1>
// <p>你好,世界!</p>
// `)
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})
const http = require('http')
const fs = require('fs')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
const url = req.url
if (url === '/') {
fs.readFile('./index.html', (err, data) => {
if (err) {
throw err
}
res.statusCode = 200
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(data)
})
} else if (url === '/assets/css/main.css') {
fs.readFile('./assets/css/main.css', (err, data) => {
if (err) {
throw err
}
res.statusCode = 200
res.setHeader('Content-Type', 'text/css; charset=utf-8')
res.end(data)
})
} else if (url === '/assets/js/main.js') {
fs.readFile('./assets/js/main.js', (err, data) => {
if (err) {
throw err
}
res.statusCode = 200
res.setHeader('Content-Type', 'text/javascript; charset=utf-8')
res.end(data)
})
} else {
res.statusCode = 404
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('404 Not Found.')
}
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})
const http = require('http')
const fs = require('fs')
const mime = require('mime')
const path = require('path')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
const url = req.url
if (url === '/') {
fs.readFile('./index.html', (err, data) => {
if (err) {
throw err
}
res.statusCode = 200
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(data)
})
} else if (url.startsWith('/assets/')) {
// /assets/js/main.js
fs.readFile(`.${url}`, (err, data) => {
if (err) {
res.statusCode = 404
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('404 Not Found.')
}
const contentType = mime.getType(path.extname(url))
res.statusCode = 200
res.setHeader('Content-Type', contentType)
res.end(data)
})
} else {
res.statusCode = 404
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('404 Not Found.')
}
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})
假如我们有一份数据 todos
需要展示到页面中
const todos = [
{ title: '吃饭', completed: false },
{ title: '睡觉', completed: true },
{ title: '打豆豆', completed: false }
]
如何将一组数据列表展示到一个页面中,最简单的方式就是字符串替换,但是如果有不止一份数据需要展示到页面中的时候就会变得非常麻烦,所以前人将此种方式整合规则之后开发了我们常见的模板引擎。
例如我们经常在网页源码中看到下面这样一段代码
<ul>
<% todos.forEach(function (item) { %>
<li><%= item.title %></li>
<% }) %>
</ul>
或者是
<ul>
{{ each todos }}
<li>{{ $value.title }}</li>
{{ /each }}
</ul>
无论如何,我们看到的这些语法都在模板引擎所指定的一些规则,目的就是让我们可以非常方便的在网页中进行字符串替换以达到动态网页的效果。
在 Node 中,有很多优秀的模板引擎,它们大抵相同,但都各有特点
const template = require('art-template')
// const ret = template.render('Hello {{ message }}', {
// message: 'World'
// })
const ret = template.render(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<h1>Hello {{ message }}</h1>
<ul>
{{ each todos }}
<li>{{ $value.title }} <input type="checkbox" {{ $value.completed ? 'checked' : '' }} /></li>
{{ /each }}
</ul>
</body>
</html>
`, {
message: 'World',
todos: [
{ title: '吃饭', completed: false },
{ title: '睡觉', completed: true },
{ title: '打豆豆', completed: false }
]
})
console.log(ret)
const http = require('http')
const template = require('art-template')
const fs = require('fs')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
const url = req.url
if (url === '/') {
fs.readFile('./index2.html', (err, data) => {
if (err) {
throw err
}
const htmlStr = template.render(data.toString(), {
message: '黑马程序员',
todos: [
{ title: '吃饭', completed: true },
{ title: '睡觉', completed: true },
{ title: '打豆豆', completed: false }
]
})
res.statusCode = 200
res.setHeader('Content-Type', 'text/html')
res.end(htmlStr)
})
}
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})
更多优秀的第三方资源 awesome node.js
类似于 HTTP, WebSocket 是一种网络通信协议。
我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?
答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起,没有请求就没有响应。
举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。
WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
其他特点包括:
建立在 TCP 协议之上,服务器端的实现比较容易。
与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
数据格式比较轻量,性能开销小,通信高效。
可以发送文本,也可以发送二进制数据。
没有同源限制,客户端可以与任意服务器通信。
协议标识符是ws
(如果加密,则为wss
),服务器网址就是 URL。
ws://example.com:80/some/path
在浏览器中提供了 WebSocket
对象用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的 API。
下面是一个简单示例
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
var ws = new WebSocket('ws://localhost:8080');
执行上面语句之后,客户端就会与服务器进行连接。
实例对象的onopen
属性,用于指定连接成功后的回调函数。
ws.onopen = function () {
// 发送消息一定要在建立连接成功以后
ws.send('Hello Server!');
}
如果要指定多个回调函数,可以使用addEventListener
方法。
ws.addEventListener('open', function (event) {
ws.send('Hello Server!');
});
实例对象的onclose
属性,用于指定连接关闭后的回调函数。
ws.onclose = function(event) {
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
// handle close event
};
ws.addEventListener("close", function(event) {
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
// handle close event
});
实例对象的onmessage
属性,用于指定收到服务器数据后的回调函数。
ws.onmessage = function(event) {
var data = event.data;
// 处理数据
};
ws.addEventListener("message", function(event) {
var data = event.data;
// 处理数据
});
注意,服务器数据可能是文本,也可能是二进制数据(blob
对象或Arraybuffer
对象)。
ws.onmessage = function(event){
if(typeof event.data === String) {
console.log("Received data string");
}
if(event.data instanceof ArrayBuffer){
var buffer = event.data;
console.log("Received arraybuffer");
}
}
除了动态判断收到的数据类型,也可以使用binaryType
属性,显式指定收到的二进制数据类型。
// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
console.log(e.data.size);
};
// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
console.log(e.data.byteLength);
};
实例对象的onerror
属性,用于指定报错时的回调函数。
socket.onerror = function(event) {
// handle error event
};
socket.addEventListener("error", function(event) {
// handle error event
});
实例对象的send()
方法用于向服务器发送数据。
ws.send('your message');
发送 Blob 对象的例子。
var file = document
.querySelector('input[type="file"]')
.files[0];
ws.send(file);
发送 ArrayBuffer 对象的例子。
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
ws.send(binary.buffer);
实例对象的 close()
方法用于关闭连接。
ws.close()
连接关闭之后会触发实例对象的
onclose
事件。
实例对象的bufferedAmount
属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。
var data = new ArrayBuffer(10000000);
socket.send(data);
if (socket.bufferedAmount === 0) {
// 发送完毕
} else {
// 发送还没结束
}
WebSocket 服务器的实现,可以查看维基百科的列表。
常用的 Node 实现有以下三种。
具体的用法请查看它们的文档,这里我们以 Socket.IO 为例。
chat
npm init -y
初始化 package.json
文件npm install express
写入以下代码
const express = require('express')
const app = express()
const http = require('http').Server(app)
app.get('/', function(req, res){
res.send('<h1>Hello world</h1>');
})
http.listen(3000, () => {
console.log('listening on *:3000')
})
将 app.get(/)
代码替换为以下内容
app.use(express.static('./public'))
npm i socket.io
const express = require('express')
const app = express()
const http = require('http').Server(app)
const io = require('socket.io')(http)
app.use(express.static('./public/'))
io.on('connection', socket => {
console.log('a user connected');
})
http.listen(3000, () => {
console.log('listening on *:3000')
})
<script src="/socket.io/socket.io.js"></script>
<script>
// 默认链接当前网页地址,也就是 ws://localhost:3000
var socket = io()
</script>
disconnect
事件io.on('connection', function(socket){
console.log('a user connected')
socket.on('disconnect', () => {
console.log('user disconnected')
})
})
socket.emit('chat message', 'hello');
socket.on('chat message', function(msg){
console.log('message: ' + msg);
});
socket.emit('request', '消息');
如果你想向除了某个套接字以外的所有人发送消息
socket.broadcast.emit('hi');
将消息发送给所有人,包括发送消息的客户端
io.emit('chat message', msg)
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
其它功能
添加“{user}正在输入”功能。 显示谁在线。 添加私人消息。 分享您的改进!
原文:https://www.cnblogs.com/liea/p/12399177.html