前言

本文的目的是基于 GatewayWorker 官方手册,梳理一次 GatewayWorker,并在实践中与 MVC 框架整合的思路(附最终的项目源码)。如果你已经理解了整合这一块儿的知识,那么就可以关掉这个网页了。时间蛮宝贵的~

这篇是上篇,梳理 GatewayWorker 基础,下篇是 GatewayWorker 与 Laravel 整合聊天室。如果你具备了 GatewayWorker 基础,请直接阅读下篇

很久以前就想做一个聊天室了。查了下 "php 通信",找到了可用的东西:Socket、WebSocket、 Workerman 以及 GatewayWorker。Socket(接口)提供了一组端到端互相通信的接口,作为通信的核心功能。Websocket(协议)定义了通信中数据的封装和显示的格式,而且最大的特点是它支持服务端向客户端的主动推送,这一点是 HTTP 做不到的。而 Workerman (框架)将这两者很好地整合在了一起(当然不仅仅于此)。GatewayWorker(框架)是在 Workerman 的基础上开发的 TCP 长连接应用框架,提供了单发、群发和广播等接口,还可以客户端和客户端通信。

所以最终我选择了 GatewayWorker 作为 Socket 监听的服务端,Laravel 作为 HTTP 请求的业务处理框架,完成一个响应式的在线聊天室(项目地址在下一篇文章最后)。

GatewayWorker工作原理

先理解一下工作原理,可以对 GatewayWorker 有个整体的把握。这一块儿其实手册里已经详细不啰嗦地解释清楚了。我这里再理一下:

1、Register、Gateway、BusinessWorker 3 种进程依次启动(因为支持多进程,所以我说“种”,而不是“个”)

2、Gateway 进程和 BusinessWorker 进程启动后向 Register 服务进程发起长连接注册自身。

3、Register 服务进程收到 Gateway 的注册后,把所有 Gateway 进程的通讯地址写入内存。

4、Register 服务进程收到 BusinessWorker 的注册后,把内存中的 Gateway 进程通讯地址发给所有 BusinessWorker 进程。

5、BusinessWorker 进程收到所有 Gateway 进程的通讯地址后,尝试连接 Gateway。

6、至此,所有 Gateway 和 BusinessWorker 进程就通过 Register 服务进程建立了长连接。

如果期间有新的 Gateway 注册到 Register(一般是分布式部署加机器),新 Gateway 的通讯地址会被广播给所有 BusinessWorker,BusinessWorker 收到通知后建立新连接。

如果期间有 Gateway 下线,Register 会收到通知、删除这个 Gateway 的内部通讯地址,并将新的内部通讯地址列表广播给所有 BusinessWorker,BusinessWorker 不再连接下线的 Gateway。

7、客户端的连接事件和连接上的数据会经由 Gateway 转发给 BusinessWorker,BusinessWorker 默认调用 Events.php 中 Events 类的 onConnect、onMessage、onClose 事件回调处理业务逻辑。

8、BusinessWorker 负责运行所有的业务逻辑,实际的处理逻辑默认在 Events.php 中实现。

GatewayWorker进程模型

GatewayWorker 是以进程的形式进驻内存的,了解了它的工作原理之后,有必要理解一下它的进程模型。

GatewayWorker 主要有 3 种进程:Register 进程、Gateway 进程和 BusinessWorker 进程。这 3 种进程分别对应了内核源码中的 Register 类、Gateway 类和 BusinessWorker 类,并且它们都是基于 Workerman 框架的 Worker 类开发的,所以这 3 种进程都有一些公共的属性,比如 name、count、onWorkerStart、onWorkerStop 等等。可以说,GatewayWorker 里所有的进程都是 Worker 进程。

1、Register进程

Register 进程主要负责 Gateway 进程 与 BusinessWorker 进程建立连接并内部通讯。

该进程由 Register 类实例化,并随进程启动进驻内存。

它可定制的只有实例化时指定自身所在的服务进程地址。包括 IP 和端口,并且目前只支持 text 协议。text 协议是 Workerman 框架定义的一种文本协议(协议格式为:数据包 + 换行符)。

2、Gateway进程

Gateway 进程主要负责客户端的连接以及连接上的数据,并将所有的请求转发给 BusinessWorker 进程进行处理。BusinessWorker 进程的所有处理结果都经由 Gateway 进程转发给客户端。

该进程由 Gateway 类实例化,并随进程启动进驻内存。

它可定制的有:

(1)实例化。指定协议、IP 和端口。

协议:目前支持的有 Websocket 协议text 协议Frame 协议自定义通讯协议和 裸 TCP 协议(不推荐,见通讯协议作用),不支持监听 HTTP 协议。

IP:"0.0.0.0" 表示监听本机所有网卡;"127.0.0.1"表示仅允许本机通过 127.0.0.1 访问该进程;内网 IP 如 "192.168.11.2" 表示只允许该 IP 访问;外网 IP 如 "110.110.110.110" 表示只允许该 IP 访问。

端口:大于 1024 小于等于 65535。小于 1024 时需要 root 权限运行该进程。

(2)name:Gateway 进程名。以便在 Bash 等终端里查看区分。

(3)count:Gateway 进程数。充分利用多 CPU 资源。默认为 1。如何设置进程数,请参考这里

(4)lanIp:Gateway 进程所在服务器的内网 IP,默认填写 "127.0.0.1" 即可。多服务器分布式部署 时要填写真实 IP。无论如何都不能填写 "0.0.0.0"。

(5)startPort:Gateway 进程启动后监听的起始端口(本机端口),用来给 BusinessWorker 进程提供连接服务,然后两者通过这个端口建立通讯。假设进程数 count 为 4,起始端口 startPort 为 2003,则 会启动 4 个 Gateway进程,各进程分别监听 2003、2004、2005、2006 端口。

(6)registerAddress:向 Register 进程的注册地址,格式为"IP + 端口",如 "127.0.0.1:1236"。和 BusinessWorker 进程指定的注册地址要保持一致。

(7)心跳设置:为了防止长时间不通讯被路由节点强行断开或断电断网等极端事件,必须加心跳。相关属性有 pingInterval、pingNotResponseLimit、pingInterval。详细心跳设置请参考服务端到客户端的心跳检测

pingInterval:心跳间隔,单位秒,0 表示不发送心跳检测。

pingNotResponseLimit:客户端连续 $pingNotResponseLimit * $pingInterval 秒内不回应心跳则断开连接。

pingData:心跳数据,可任意,客户端能识别就行。

(8)onWorkerStart:Gateway 进程启动后的回调函数。

(9)onWorkerStop:Gateway 进程关闭的回调函数。

(10)onConnect:当有客户端连接上来时触发。与 Events::onConnect 的区别是 Events::onConnect 方法运行在 BusinessWorker 进程上。而 Gateway::onConnect 方法是运行在Gateway 进程上,无法使用 \GatewayWorker\Lib\Gateway 类提供的接口。

(11)onClose:当有客户端连接关闭时触发。同样与Events::onClose的区别是 Gateway::onClose 方法是运行在 Gateway 进程上,无法使用 \GatewayWorker\Lib\Gateway 类提供的接口。

3、BusinessWorker进程

BusinessWorker 进程负责运行业务逻辑。BusinessWorker 进程收到 Gateway 进程转发来的事件和请求时,会默认调用 Events.php 中的 onConnect、onMessage、onClose 方法处理事件和数据。

该进程由 BusinessWorker 类实例化,并随进程启动进驻内存。

它可定制的有:

(1)name:BusinessWorker 进程名。以便在 Bash 等终端里查看区分。

(2)count:BusinessWorker 进程数。充分利用多 CPU 资源。默认为 1。如何设置进程数,请参考这里

(3)registerAddress:向 Register 进程的注册地址,格式为"IP + 端口",如 "127.0.0.1:1236"。和 Gateway 进程指定的注册地址要保持一致。

(4)onWorkerStart:BusinessWorker 进程启动后的回调函数

(5)onWorkerStop:BusinessWorker 进程关闭的回调函数。

(6)eventHandler:指定 BusinessWorker 进程里实际处理业务逻辑的类,默认是 Events。也就是默认使用 Events.php 中的 Events 类来处理业务。业务类至少要实现onMessage 静态方法,onConnect 和 onClose 静态方法可以不用实现。(如果使用了命名空间,建议填写完全限定名称的命名空间。)

Events.php

上面提到了 Events.php,它是实际处理业务逻辑的类 Events 所在的文件。我们在实际的开发中,只需要关注这一个文件。

Events 里有 5 个事件回调的处理方法,按照发生顺序,依次是

  • onWorkerStart (Worker $businessWorker):当 BusinessWorker 进程启动时触发。每个进程生命周期内只触发一次。
  • onConnect (string $client_id):当客户端连接上 Gateway 进程时触发(TCP 三层握手)。

  • onMesssge (string $client_id, mixed $recv_data):当客户端发来数据,也就是 Gateway 进程收到数据后触发。
  • onClose (string client_id):当客户端连接断开时触发。无论是客户端还是服务端主动断开,都会触发。

  • onWorkerStop (Worker $businessWorker):当 BusinessWorker 进程退出时触发。每个进程生命周期内只触发一次。

这里面我们常用到的是 onMessage 和 onClose 回调,其他比较少用。

上面的回调事件里有一个比较重要的参数:$client_id。client_id 是 20 个字符的定长字符串,用来全局标识一个 Socket 连接。每个客户端连接都会被分配一个全局唯一的 client_id。客户端关闭连接时,对应的 client_id 会失效。当客户端再次打开一个 Socket 连接时,会被分配一个新的 client_id。

Lib\Gateway类提供的接口

既然(默认)在 Events.php 中处理实际的业务逻辑,回调的事件我们已经知道了。那么怎么向客户端发送消息呢?

命名空间 \GatewayWorker\Lib\Gateway 指向的这个 Gateway 类,提供了一组单发、群发和广播的接口,在 Events.php 中向客户端发信的时候就可以使用这个类。它提供的接口非常丰富:

Gateway::sendToAll($data);      // 向所有客户端发送数据
Gateway::sendToClient($client_id, $data); // 向某个客户端发送数据
Gateway::closeClient($client_id); // 关闭某个客户端
Gateway::isOnline($client_id); // 判断某客户端连接是否在线
Gateway::bindUid($client_id, $uid); // 绑定 uid 与 client_id
Gateway::unbindUid($client_id, $uid); // 取消 uid 与 某个 client_id 的绑定
Gateway::isUidOnline($uid); // 某个 uid 是否在线
Gateway::GetClientIdByUid(); // 获取与 uid 绑定的 client_id 列表(一对多)
Gateway::sendToUid($uid, $data); // 向所有 uid 发送
Gateway::joinGroup($client_id, $group); // 把该 client_id 加入群组
Gateway::leaveGroup($client_id, $group); // 将 client_id 离开群组
Gateway::sendToGroup($group, $data); // 向某群组 group 发送
Gateway::getClientCountByGroup($group); // 获取某个组的在线连接数
Gateway::getClientSessionsByGroup($group); // 获取某个组的连接信息
Gateway::getClientInfoByGroup($group); // getClientSessionsByGroup 的别名
Gateway::getAllClientCount(); // 获取所有的在线连接数
Gateway::getAllClientSessions(); // 获取所有在线用户的 session
Gateway::getAllClientInfo(); // getAllClientSessions 的别名
Gateway::setSession($client_id, $session); // 设置 session,原 session 值会被覆盖
Gateway::updateSession($client_id, $session); // 更新 session,实际上是与旧的session合并
Gateway::getSession($client_id); // 获取某个 client_id的 session

这里面比较重要的是 GatewayWorker 的超全局数组 $_SESSION。每个客户端连接对应一个 Session 会话,并由 Gateway 进程存储在内存里。示例如下,在收到客户端消息时,打印所有在线连接的 Session:

use \GatewayWorker\Lib\Gateway;

class Events
{
...
public onMessage($client_id, $message)
{
$_SESSION['name'] = $message['name']; // 操作当前用户的 Session 时,直接使用 $_SESSION 即可
var_export(Gateway::getAllClientSessions());
}
...
}
打印出的数据类似如下: array(
'7f00000108fc00000008' => array('name' => 'Tom'),
'7f00000108fc00000009' => array('name' => 'Joan')
)

注意上面的注释,操作当前连接上的 Session 时,直接使用 $_SESSION['xx'] = 'xxx'; 的方式赋值即可,操作其他用户的 Session 时用  Gateway::setSession 接口。

此外,如果你在 GatewayWorker 的进程模型里需要获取客户端、服务端的 IP,请使用 $_SERVER 数组。它由 Workerman 框架定义,内置了 5 个数组成员,数组 key 分别如下,详细请参考文档

REMOTE_ADDR   // 客户端IP(如果客户端处于局域网,则是客户端所在局域网的出口IP)
REMOTE_PORT // 客户端端口(如果客户端处于局域网,则是客户端所在局域网的出口端口)
GATEWAY_ADDR // Gateway 进程所在服务器的 IP
GATEWAY_PORT // Geteway 监听的端口,这对于多端口应用中在 Events.php 里区分客户端连的是哪个端口非常有用。
GATEWAY_CLIENT_ID // 全局唯一的客户端 IP

好的。有关 GatewayWorker 框架的基础暂时就梳理这么多。更多 GatewayWorker 开发和部署的细节或问题,比如心跳检测、设置定时器、合理选择多进程、分布式部署、定制通讯协议、启用 wss 协议等等,都在文档里有详细的介绍。车在下面。

我感觉这一篇有点长了,所以将在下一篇开始梳理 GatewayWorker 与 Laravel 框架的整合。

相关链接

GatewayWorker 在线文档:http://doc2.workerman.net/326102

Workerman 在线文档:http://doc.workerman.net/

Workerman 官网:https://www.workerman.net/

聊天室(上篇)GatewayWorker 基础的更多相关文章

  1. 聊天室(下篇)GatewayWorker 与 Laravel 的整合

    思路 上一篇大概梳理了一下 GatewayWorker 的基础知识.这篇就来准备整合 GatewayWorker 到 Laravel. GatewayWorker 是基于 Socket 监听的服务器框 ...

  2. 【视频】零基础学Android开发:蓝牙聊天室APP(四)

    零基础学Android开发:蓝牙聊天室APP第四讲 4.1 ListView控件的使用 4.2 BaseAdapter具体解释 4.3 ListView分布与滚动事件 4.4 ListView事件监听 ...

  3. 【视频】零基础学Android开发:蓝牙聊天室APP(二)

    零基础学Android开发:蓝牙聊天室APP第二讲 2.1 课程内容应用场景 2.2 Android UI设计 2.3 组件布局:LinearLayout和RelativeLayout 2.4 Tex ...

  4. 【视频】零基础学Android开发:蓝牙聊天室APP(三)

    零基础学Android开发:蓝牙聊天室APP第三讲 3.1 ImageView.ImageButton控件具体解释 3.2 GridView控件具体解释 3.3 SimpleAdapter适配器具体解 ...

  5. 【视频】零基础学Android开发:蓝牙聊天室APP(一)

    零基础学Android开发:蓝牙聊天室APP第一讲 1. Android介绍与环境搭建:史上最高效Android入门学习 1.1 Google的大小战略 1.2 物联网与云计算 1.3 智能XX设备 ...

  6. 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据

    ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据   最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前 ...

  7. 三、jQuery--jQuery基础--jQuery基础课程--第12章 jQuery在线聊天室

    在线聊天室案例 一.功能简介: 1.用户需要登录后才能进入聊天室交流 2.已无刷新的方式,动态展示交流后的内容和在线人员的基本信息 3.登录后的用户可以提交文字和表情图标 技术重点:利用ajax的无刷 ...

  8. JAVA基础知识之网络编程——-基于TCP通信的简单聊天室

    下面将基于TCP协议用JAVA写一个非常简单的聊天室程序, 聊天室具有以下功能, 在服务器端,可以接受客户端注册(用户名),可以显示注册成功的账户 在客户端,可以注册一个账号,并用这个账号发送信息 发 ...

  9. Java基础知识强化之网络编程笔记05:UDP之多线程实现聊天室案例

    1. 通过多线程改进刚才的聊天程序,这样我就可以实现在一个窗口发送和接收数据了 2.  代码示例: (1)SendThread.java,如下: package com.himi.udpDemo2; ...

随机推荐

  1. Robot Framework 的安装配置和简单的实例介绍

    Robot Framework 介绍 Robot Framework 是一款基于 Python 的功能自动化测试框架.它具备良好的可扩展性,支持关键字驱动,可以同时测试多种类型的客户端或者接口,可以进 ...

  2. D-Separation(D分离)-PRML-8.22-Graphical Model 五 18 by 小军

    D-Separation(D分离)-PRML-8.22-Graphical Model 五18by 小军   一.引言 在贝叶斯网络的学习过程中,经常会遇到(D-Separation)D-分离这个概念 ...

  3. 再谈MySql索引

    一.索引简介 MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度. 索引分单列索引(主键索引.唯一索引.普通索引)和组合索引.单列索引,即一个索引只包含单个列 ...

  4. 每个 JavaScript 工程师都应懂的33个概念

    简介 这个项目是为了帮助开发者掌握 JavaScript 概念而创立的.它不是必备,但在未来学习(JavaScript)中,可以作为一篇指南. 本篇文章是参照 @leonardomso 创立,英文版项 ...

  5. jquery的serializeArray、param 与serializeArray 的区别与源码解析

    jQuery.param( obj, traditional ) 为url查询或者ajax 将对象或者数组转为url参数或ajax参数,是挂在jQuery对象上的静态方法,有码有真相: var myI ...

  6. typescript数据类型

    // 布尔类型 let isDone: boolean = false; // 数字类型 所有数字都是浮点数 number let decLiteral: number = 6; let hexLit ...

  7. 【loj2586】【APIO2018】选圆圈

    题目 有 \(n\) 个圆$c_1,c_2, \cdots , c_n $,执行如下的操作: 找到剩下的半径最大的圆删除并删除所有和它有交的其他并没有被删除的圆: 求每个圆是被那个圆删除的: $1 \ ...

  8. TIME_WAIT状态的一些总结

    前言: TCP断开连接的四次握手中, 主动关闭连接的一方的TIME_WAIT状态尤为重要. 1:TCP连接的三次握手和断开的四次挥手 2:由上图可知 在主动关闭的一方, 会经历TIME_WAIT状态, ...

  9. Ansible lineinfile模块详解

    目录 简介 修改匹配行 在匹配行前或后添加内容 在匹配行前添加 在匹配行后添加 修改文件内容及权限 删除一行内容 文件存在则添加一行内容 如果有匹配的行则修改该行,如果不匹配则添加 参数backref ...

  10. 在 mac 上添加想要的命令

    习惯了 Linux 下的 ll 命令,Mac 终端没有这个命令很不舒服.可以将 ll 别名解决这个问题: 打开终端,进入到当前根目录 cd ~ 编辑.bash_profile文件 vim .bash_ ...