Git Repository
如果你不想读本文,只想看一代码,那么你就可以打开这个东西。
👉👉👉项目位置
伏笔
- 在二维码横行的时代,各大网站都实现了扫描二维码登录的用户体验度极高的功能,我常常在如何实现这样的效果呢?于是,为本篇文章埋下伏笔。
- 本人为PHPer,常年在开发企业级的ERP系统,在不安于现状的情况下,我想尽可能让用户体验达到最好,于是用Vue开发了ERP其中的一个子系统,反响不错。但本人不安于现状,想着去玩点新鲜的。这时,即时的消息通知和数据更新便出现在了我的脑海了。于是,为此文又埋下伏笔。
- 在之前有同学问我<广播事件>的相关知识点,但鉴于才疏学浅,不曾涉猎此领域,于是就敷衍了事,但在我心里埋下了一颗种子。
- 正处于双11时间点,本人又是一个11,又没有什么朋友,于是一个人在家又读起Laravel文档来,当读到Laravel<广播事件>时,感觉有必要研究一下了,于是此文诞生。
准备
广播事件在Laravel的实现驱动有两种,其一为Pusher
,其二为Socket.io + Redis
。
Pusher
在文档里有阐释如何使用,包括demo
等。使用驱动需要注册账号、安装组件等(好像还是要花钱的,毕竟人家是第三方服务),于是本人就没去体验。
Socket.io
+ Redis
在官方文档里面有阐释其原理,但是没有一个demo
,这就让上手难度增加了不少。不过在Laravel社区中有一个配合驱动的一个WebSocket服务器项目,大家可以下载来尝试,本人尝试了,但确实笔者比较愚钝,依然没整明白。
但当我读到这的时候,我才真正的明白了其中原理。
Redis 广播器会使用 Redis 的「生产者/消费者」特性来广播消息;尽管如此,你仍需将它与 WebSocket 服务器一起使用。WebSocket 服务器会从 Redis 接收消息,然后再将消息广播到你的 WebSocket 频道上去。
当 Redis 广播器发布一个事件时,该事件会被发布到它指定的频道上去,传输的数据是一个采用 JSON 编码的字符串。该字符串包含了事件名、 data 数据和生成该事件套接字 ID 的用户(如果可用的话)。
环境
- Mac 2016 <MACOS 10.13.1>
- Node v7.1.0
- NPM v4.2.0
- Redis server v=3.2.8
- socket.io v2.0.4(NPM 安装)
- ioredis v3.2.1(NPM 安装)
- PHP 7.1
- Laravel 5.5
整理所用知识
开始干活之搭建HTTP Server服务器
- 安装node环境(其它系统请自查)
$ brew install node
$ node -v
$ npm -v
第一步安装node,二三步检查版本
npm如果下载不下来组件,请更换为cnpm淘宝镜像
- 初始化项目
$ mkdir ~/node-socket
$ cd ~/node-socket
$ npm init
第一步创建项目目录,二步进入目录,三步初始化项目(回车大法解决一切)
- 在项目根目录下创建
index.js
和index.html
文件
- index.html
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
- index.js
var app = require('http').createServer(handler)
var fs = require('fs');
function handler(req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
app.listen(3000);
console.log('Lisening http://localhost:3000');
解读:
第一行:使用node核心模块http
创建Http服务器。
第二行:引用node核心模块文件模块fs
函数handler:服务器接受到请求的回调函数。接收两个参数request
和response
,请求与响应。其函数体具体含义为,读取__dirname绝对路径下的index.html
文件数据,如果读取失败,那么抛出500的异常;如果读取成功,则响应具体读取到的数据。
最后:监听3000端口
- 开启服务
在项目根目录执行第一行,并随之出现二行的命令行提示信息,即服务器已启动。
$ node index.js
$ Lisening http://localhost:3000
- 打开http://localhost:3000,出现如下画面你就成功了。
开始干活之配置WebSocket服务器
- 安装
socket.io
cnpm install socket.io --save
--save是保存到
package.json
文件,用于阐述项目依赖,与composer.json
异曲同工
- 文件
- index.html
<html>
<head>
<meta charset="UTF-8">
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<h1>Hello World</h1>
<script>
// 监听地址、端口一定与自己开启的HTTP SERVER的地址与端口一致。
var socket = io('http://localhost:3000');
// Socket连接成功的事件,当连接成功后,执行回调,log输出socket.id及自定义信息
socket.on('connect', () => {
console.log(socket.id);
console.log('Connecting...');
});
// 监听新闻事件,与后台的事件名称必须一致,否则不能实现效果。
socket.on('news', function (data) {
// 当监听到news事件后,输出data信息;然后再向服务器发送back事件。
// 如果后台同时监听了此事件,那么后台在监听到此事件后,会进行相应逻辑处理。
// 这样就实现了Web端与服务端的相互通讯。
console.log(data);
socket.emit('back', { my: 'data' });
});
</script>
</body>
</html>
- index.js
var app = require('http').createServer(handler)
var fs = require('fs');
var io = require('socket.io')(app);
// Socket连接事件,监听到此事件后,执行相应的回调。
io.on('connection', function (socket) {
// 向前台发送news事件,并携带json数据
socket.emit('news', { hello: 'world' });
// 监听前台发送的back事件
socket.on('back', function (data) {
console.log(data);
});
});
function handler(req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
app.listen(3000);
console.log('Lisening http://localhost:3000');
- 测试
开启服务$ node index.js
-
web端
-
服务器端
开始干活之Node.js与Redis的关联使用
- 测试与Redis的连接(前提是你安装并开启了redis服务器,至于怎么安装请自行查阅google)
- index.js
...省略
var Redis = require('ioredis');
var redis = new Redis();
redis.set('foo', 'bar');
redis.get('foo', function (err, result) {
console.log(result);
});
...省略
发现如下画面,你就成功了。
-
服务器端
-
Redis客户端(Redis如果不会请自行查询文档)
- 订阅与发布
- index.js
var app = require('http').createServer(handler)
var fs = require('fs');
var io = require('socket.io')(app);
var Redis = require('ioredis');
var redis = new Redis();
var pub = new Redis();
// 订阅channel频道 system,可以订阅多个频道,err指的是错误,count指的订阅频道个数
redis.subscribe('system', function (err, count) {
// 当订阅成功之后,在频道内部发布一条反馈信息
pub.publish('system', 'Listening The System Channel');
console.log('Listening System Channel');
});
// Socket连接事件,当连接成功后触发回调函数
io.on('connection', function (socket) {
// 向前台发送news事件,并携带json数据
socket.emit('news', { hello: 'world' });
// 监听前台发送的back事件
socket.on('back', function (data) {
console.log(data);
});
// 监听已订阅频道发来的信息事件 message event
redis.on('message', function (channel, message) {
// channel指的是频道,message回传的信息
console.log('Receive message %s from channel %s', message, channel);
// 向前台发送news事件
socket.emit('news', {message: message});
});
});
function handler(req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
app.listen(3000);
console.log('Lisening http://localhost:3000');
- index.html
<html>
<head>
<meta charset="UTF-8">
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<h1>Hello World</h1>
<script>
// 监听地址、端口一定与自己开启的HTTP SERVER的地址与端口一致。
var socket = io('http://localhost:3000');
// Socket连接成功的事件
socket.on('connect', () => {
console.log(socket.id);
console.log('Connecting...');
});
// 新闻事件,与后台的触发的事件必须一致,否则不能实现效果
socket.on('news', function (data) {
// 当监听到news事件后,向服务器发送back事件,实现前后台的相互通讯
console.log(data);
socket.emit('back', { my: 'data' });
});
</script>
</body>
</html>
- 测试及效果
启动node index.js
服务,然后使用redis-cli
在 systemchannel
频道中发布一条信息I am System Message6
,然后效果如下:
-
服务端
-
Web端
-
Redis端
开始干活之在PHP
中使用Redis
发布信息,在Node
服务端订阅信息,利用Socket.io
实现Web的即时信息展示。
- 打开Laravel项目,路由文件中写入
Route::get('/socket', function () {
\Redis::publish('system', '我是一个系统级别的信息,我来自PHP');
return 'success';
});
- 访问对应路由
-
PHP部分
-
Node服务端
-
Web端
大功告成
基本实现流程就这些。但是笔者暂时还没有深入研究其中奥义所在。
比如使用Laravel的事件广播、用户识别、不同频道的不同信息处理、以及Web端回传数据,PHP端写入数据库、触发响应的事件等等。这些都需要时间进行研究,不过大致的流程已经跑通,相信之后的路会越走越通。
本文暂时未完,还有二系列的发布,敬请期待。