Node.js第十五篇:Socket.IO
第一章:认识Socket.IO
1.1-WebSocket
传统的客户端和服务器通信协议是HTTP:客户端发起请求,服务端进行响应,服务端从不主动勾搭客户端。
这种模式有个明显软肋,就是同步状态。而实际应用中有大量需要客户端和服务器实时同步状态的场景,比如聊天室、股票行情、在线共享文档等都需要客户端实时拿到服务器的最新状态。
针对这种实时同步的需求,一种简单的方式是轮询,比如每隔5s发一次http请求去拿服务器最新的状态数据。但这种方式会存在数据延迟,浪费带宽等副作用。
更完美的方式是使用WebSocket,浏览器原生支持,W3C标准协议,客户端和服务器建立持久性连接可以互发消息。
1.2-Socket.IO是什么
socket.io 是一个类库,内部封装了WebSocket,可以在浏览器与服务器之间建立实时通信。
如果某些旧版本的浏览器不支持WebSocket,socket.io会使用轮询代替。另外它还具有可发送二进制消息、多路复用、创建房间等特性,因此相比直接使用原生WebSocket,socket.io是更好的选择。
开发一个实时应用主要分两部分:服务端和客户端,socket.io分别提供了相应的模块供我们方便地调用。
1.3-什么是Socket
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
作用:完成两个应用程序之间的数据传输
1.4-常用的API
服务端
io对象
- on方法用来监听事件
io.on('connection',socket=>{ })
- connection对象监听客户端连接
- socket表示当前进入的客户端socket对象
emit('事件名称',数据
) 服务端向所有客户端发送消息- 事件名称,客户端或服务端约定好向对方发送消息对方要触发的事件
- 数据,传递的信息
io.to(roomid).emit('事件名称', 数据)
向指定的属于同一组的客户端发送消息- roomid 分组名称表示
socket对象
- on方法用来监听事件
socket.on('disconnect',()=>{})
- 若有一个客户端端口连接,则会触发该事件
socket.on('自定义其他事件名称',(data)=>{})
- 自定其他事件名,客户端或服务端约定好对方发送消息时,用哪个事件触发
- data,接收对方发送过来的数据
id
属性,socket对象的标识emit('事件名称',数据)
向对应的客户端发送消息- 事件名称,客户端或服务端约定好向对方发送消息对方要触发的事件
- 数据,传递的信息
join(roomid)
加入某个分组- roomid,字符串,可以自定义
leave(roomid)
从某个分组中脱离socket.broadcast.to(roomid).emit('事件名称', 数据)
向所有客户端(同属一组,除了自己)发送数据
客户端
io对象
- i
o(url)
连接指定的服务端,并返回一个sokcet对象- url,服务端连接地址,如:
http://localhost
- url,服务端连接地址,如:
socket对象
socket.on('connect', function () {})
客户端和服务端建立连接成功后要触发的事件socket.on('disconnect', function () {})
客户端和服务端断开连接要触发的事件(比如服务器崩溃)socket.on('自定义其他事件名称',(data)=>{})
- 自定其他事件名,客户端或服务端约定好对方发送消息时,用哪个事件触发
- data,接收对方发送过来的数据
socket.emit('事件名称',数据
) 客户端向服务端发送消息
第二章:Socket.IO快速入门
在Node.js中使用Socket.IO
2.1-需求
- 服务端和客户端建立连接
- 客户端和服务端建立连接,并向服务端发送一条消息
- 服务端向所有客户端,发送一条消息
- 有一个客户端离线,服务端可以接受到通知
2.2-服务端程序
导入第三方模块npm install socket.io
const http = require("http");
const url = require("url");
const path = require("path");
const fs = require("fs");
const mime = require("mime");
// 创建服务对象
const app = http.createServer();
// 监听请求,处理静态资源
app.on("request", (req, res) => {
// 获取请求的路径
let { pathname } = url.parse(req.url, true);
// 拼接服务器上文件的物理路径
let realPath = path.join(__dirname, "public", pathname);
// 获取请求的资源类型
let type = mime.getType(realPath);
// 读取服务器本地文件
fs.readFile(realPath, (err, data) => {
if (err) {
res.writeHead(404,{"Content-type":type+";charset=utf-8"});
res.end("访问资源不存在");
return;
}
res.writeHead(200);
res.end(data);
});
});
// 【重点!!-导入socket.io并和http服务对象关联】
const io = require('socket.io')(app)
// 【重点!!-检测客户端连接进入】
io.on('connection', socket => {
console.log('一个客户端进入')
// 注册to-server事件,接收客户端向服务端发送的数据
socket.on('to-server', (data) => {
console.log('客户端说: ' + data)
// 向对应的客户端发送数据
socket.emit('to-client', '我是服务端数据')
// 向所有在线的客户端发送数据
// io.emit('to-client','我是服务端数据')
})
// 检测一个客户端断开连接
socket.on('disconnect', () => {
console.log('一个客户端离开')
})
})
// 开启端口4000
app.listen(4000);
服务端启动成功后,客户端可以通过http://127.0.0.1:4000/socket.io/socket.io.js
在客户端操作socket
2.3-客户端程序
index.html
<input type="text" id="message"><button id="btn">发送</button>
<!-- 导入客户端socket.io.js -->
<script src="http://127.0.0.1:4000/socket.io/socket.io.js"></script>
<script>
// 创建socket对象,设置要连接的服务器url
var socket = io('http://127.0.0.1:4000');
// 注册connect事件,监听和服务是否建立了连接
socket.on('connect', function () {
console.log('客户端和服务建立了连接')
})
// 注册disconnect事件,监听和服务是否断开连接
socket.on('disconnect', function () {
console.log('客户端和服务端断开连接了');
})
// 注册to-client事件,监听服务端向客户端传送的数据
socket.on('to-client', function (data) {
console.log('服务端说:' + data);
})
// 点击按钮向服务端发送数据
btn.onclick = function() {
var val = message.value
socket.emit('to-server',val)
}
</script>
2.4-测试
- 打开多个客户端:http://127.0.0.1:4000/index.html
- 在控制台查看服务端发送的消息
- 在服务端控制台中查看客户端发送的消息
第三章:Express中使用Socket.IO
3.1-基本使用
服务端程序
const express = require('express')
const path = require('path')
const app = express()
const statiPath = path.join(__dirname, './public')
app.use(express.static(statiPath))
// 【重点-通过http模块Server方法获取关联Express的服务对象】
const server = require('http').Server(app);
// 【重点-导入socket.io模块获取io对象并关联http服务对象】
const io = require('socket.io')(server);
// 【重点-检测客户端和服务端是否连接成功】
io.on('connection', socket => {
console.log('有一个客户端连接成功')
// 监听当前客户端向服务端发送的数据
socket.on('message', data => {
console.log('客户端说:' + data)
// 服务端向所有客户端发送消息
io.emit('message',data)
})
})
// 【重点-使用server监听80端口】
server.listen(80)
客户端程序
index.html
<input type="text" id="msg"><button id="btn">发送</button>
<script src="http://localhost/socket.io/socket.io.js"></script>
<script>
// 客户端创建socket对象,并配置连接服务器url
var socket = io('http://localhost')
// 监听服务端发送过来的数据
socket.on('message',function(data){
console.log(data)
})
// 点击按钮向服务端发送数据
btn.onclick = function(){
socket.emit('message',msg.value)
}
</script>
3.2-发送文字和图片
服务端程序
const express = require('express')
const path = require('path')
const app = express()
const statiPath = path.join(__dirname, './public')
app.use(express.static(statiPath))
// 【重点-通过http模块Server方法获取关联Express的服务对象】
const server = require('http').Server(app);
// 【重点-导入socket.io模块获取io对象并关联http服务对象】
const io = require('socket.io')(server);
// 【重点-检测客户端和服务端是否连接成功】
io.on('connection', socket => {
console.log('有一个客户端连接成功')
// 监听当前客户端向服务端发送的数据-文本消息
socket.on('message', data => {
console.log('客户端说:' + data)
// 服务端向所有客户端发送消息
io.emit('message',data)
})
// 监听当前客户端向服务端发送的数据-文件消息
socket.on('image', data => {
// 向其他客户端发送文件
io.emit('image', data);
})
})
// 【重点-使用server监听80端口】
server.listen(80)
客户端程序
<div class="talk">
<!-- 聊天记录 -->
<div class="box">
<ul id="ul"></ul>
</div>
<!-- 发送文本消息 -->
<p>
<textarea id="msg" placeholder="请输入内容"></textarea>
<!-- 按钮 -->
<button id="btn">发送</button>
<button id="btn2">图片</button>
<input type="file" style="display:none" id="fileDom">
</p>
</div>
<script src="http://localhost/socket.io/socket.io.js"></script>
<script>
// 客户端创建socket对象,并配置连接服务器url
var socket = io('http://localhost')
// 监听服务端发送过来的数据-文本
socket.on('message', function (data) {
var li = document.createElement('li');
li.innerText = data;
ul.appendChild(li)
})
// 监听服务端发送过来的数据-文本
socket.on('image', function (data) {
var li = document.createElement('li');
li.innerHTML = '<img src="'+data+'">';
ul.appendChild(li)
})
// 点击按钮向服务端发送数据-文字
btn.onclick = function () {
socket.emit('message', msg.value)
}
// 点击按钮向服务端发送数据-图片
btn2.onclick = function () {
fileDom.click()
}
// 上传事件触发
fileDom.onchange = function() {
// 获取读取的文件
var file = fileDom.files[0];
// 创建fileReader对象
var reader = new FileReader();
// 读取文件内容
reader.readAsDataURL(file)
// 读取完毕后,发送到服务端
reader.onload = function(ev){
// console.log(reader.result)
// 发送文件数据
socket.emit('image',reader.result)
}
}
</script>
第四章:Koa中使用Socket.IO
导入第三方模块 npm install koa-socket-2
参考文档:https://www.npmjs.com/package/koa-socket-2
服务端程序
const koa = require('koa')
const router = require('koa-router')()
const path = require('path')
const static = require('koa-static')
// 【导入koa-socket-2模块】
const IO = require('koa-socket-2');
// 创建koa服务对象
const app = new koa()
// 【创建IO对象】
const io = new IO();
// 【和koa服务对象关联】
io.attach(app)
// 定义roomid,实现分组
let roomid = 'group';
// 【监听客户端连接服务】
app._io.on('connection', socket => {
console.log('有新的客户端进入')
// 新的客户端socket加入组中
socket.join(roomid);
// 【监听客户端发送的数据】
socket.on('message', data => {
console.log('客户端发送的数据是:' + data)
})
// 服务端向当前客户端发送数据
socket.emit('message', '对您广播:您好,同志')
// 服务端向所有客户端发送数据
app._io.emit('message', '全员广播:同志们,咱们大家好')
// 服务端向所有客户端(同属一组,除了自己)发送数据
socket.broadcast.to('group').emit('message', '小组中的朋友们,大家好')
// 服务端向所有客户端(同属一组,包括了自己)发送数据
app._io.to('group').emit('message', '小组中的朋友们,大家好2')
// 检测一个客户端离线
socket.on('disconnect', () => {
// socket.id 获取socket的唯一标识
console.log('id为' + socket.id + '客户端离线了');
})
});
// 配置路由和静态资源
app.use(router.routes())
app.use(router.allowedMethods());
app.use(static(path.join(__dirname,'./public')))
app.listen(80)
客户端程序
<input type="text" id="text">
<button id="btn">向服务端发送数据</button>
<script src="http://localhost/socket.io/socket.io.js"></script>
<script>
// 连接服务端,并创建客户端socket对象
var socket = io('http://localhost');
// 点击按钮向服务端发送数据
btn.onclick = function() {
var val = text.value;
socket.emit('message',val);
}
// 监听服务端发送的数据
socket.on('message',function(data){
console.log('服务端说:' + data);
})
</script>
第五章:案例-Open聊天室
5.1-需求
登录界面
- 用户进入登录页面
- 用户输入昵称,点击进入聊天室
- 后端检测,该用户是否存在(是否已经存在有该昵称的socket)
- 存在,则提示用户更换用户名
- 不存在,则允许用户进入聊天室,并在后端系统中保存该用户的socket
聊天界面
- 功能1:展示出所有在线用户
- 功能2:群聊
- 功能3:私聊
- 功能4:上线消息提醒
- 功能5:发送图片
- 功能6:新消息提醒
5.2-页面交互流程图
通过流程图直观了解业务
5.3-代码下载
代码没有做详细优化,后续会更新