PHP+Node.js+Redis+Socket.io实现Web端即时通讯(一)

Git Repository

如果你不想读本文,只想看一代码,那么你就可以打开这个东西。
👉👉👉项目位置

伏笔

  1. 在二维码横行的时代,各大网站都实现了扫描二维码登录的用户体验度极高的功能,我常常在如何实现这样的效果呢?于是,为本篇文章埋下伏笔。
  2. 本人为PHPer,常年在开发企业级的ERP系统,在不安于现状的情况下,我想尽可能让用户体验达到最好,于是用Vue开发了ERP其中的一个子系统,反响不错。但本人不安于现状,想着去玩点新鲜的。这时,即时的消息通知和数据更新便出现在了我的脑海了。于是,为此文又埋下伏笔。
  3. 在之前有同学问我<广播事件>的相关知识点,但鉴于才疏学浅,不曾涉猎此领域,于是就敷衍了事,但在我心里埋下了一颗种子。
  4. 正处于双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 的用户(如果可用的话)。

环境

  1. Mac 2016 <MACOS 10.13.1>
  2. Node v7.1.0
  3. NPM v4.2.0
  4. Redis server v=3.2.8
  5. socket.io v2.0.4(NPM 安装)
  6. ioredis v3.2.1(NPM 安装)
  7. PHP 7.1
  8. Laravel 5.5

整理所用知识

  1. Redis发布与订阅
  2. 使用Node.js搭建Socket服务器
  3. 使用Node.js操作Redis
  4. HTML页面中使用socket.io组件实现信息的接收与发布

开始干活之搭建HTTP Server服务器

  1. 安装node环境(其它系统请自查)
$ brew install node
$ node -v
$ npm -v 

第一步安装node,二三步检查版本
npm如果下载不下来组件,请更换为cnpm淘宝镜像

  1. 初始化项目
$ mkdir ~/node-socket
$ cd ~/node-socket
$ npm init

第一步创建项目目录,二步进入目录,三步初始化项目(回车大法解决一切)

  1. 在项目根目录下创建index.jsindex.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:服务器接受到请求的回调函数。接收两个参数requestresponse,请求与响应。其函数体具体含义为,读取__dirname绝对路径下的index.html文件数据,如果读取失败,那么抛出500的异常;如果读取成功,则响应具体读取到的数据。
最后:监听3000端口

  1. 开启服务
    在项目根目录执行第一行,并随之出现二行的命令行提示信息,即服务器已启动。
$ node index.js
$ Lisening http://localhost:3000
  1. 打开http://localhost:3000,出现如下画面你就成功了。
    image.png

开始干活之配置WebSocket服务器

  1. 安装socket.io
cnpm install socket.io --save

--save是保存到package.json文件,用于阐述项目依赖,与composer.json异曲同工

  1. 文件
  • 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');
  1. 测试
    开启服务$ node index.js
  • web端


    image.png
  • 服务器端


    image.png

开始干活之Node.js与Redis的关联使用

  1. 测试与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);
});
...省略

发现如下画面,你就成功了。

  • 服务器端


    image.png
  • Redis客户端(Redis如果不会请自行查询文档)


    image.png
  1. 订阅与发布
  • 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>
  1. 测试及效果
    启动node index.js服务,然后使用redis-cli在 system channel频道中发布一条信息I am System Message6,然后效果如下:
  • 服务端


    image.png
  • Web端


    image.png
  • Redis端


    image.png

开始干活之在PHP中使用Redis发布信息,在Node服务端订阅信息,利用Socket.io实现Web的即时信息展示。

  1. 打开Laravel项目,路由文件中写入
Route::get('/socket', function () {
    \Redis::publish('system', '我是一个系统级别的信息,我来自PHP');
    return 'success';
});
  1. 访问对应路由
  • PHP部分


    image.png
  • Node服务端


    image.png
  • Web端


    image.png

大功告成

基本实现流程就这些。但是笔者暂时还没有深入研究其中奥义所在。
比如使用Laravel的事件广播、用户识别、不同频道的不同信息处理、以及Web端回传数据,PHP端写入数据库、触发响应的事件等等。这些都需要时间进行研究,不过大致的流程已经跑通,相信之后的路会越走越通。
本文暂时未完,还有二系列的发布,敬请期待。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容