本文的目的是基于 GatewayWorker 官方手册,梳理一次 GatewayWorker,并在实践中与 MVC 框架整合的思路(附最终的项目源码)。若是你已经理解了整合这一起的知识,那么就能够关掉这个网页了。时间蛮宝贵的~php
这篇是上篇,梳理 GatewayWorker 基础,下篇是 GatewayWorker 与 Laravel 整合聊天室。若是你具有了 GatewayWorker 基础,请直接阅读下篇。html
好久之前就想作一个聊天室了。查了下 "php 通讯",找到了可用的东西:Socket、WebSocket、 Workerman 以及 GatewayWorker。Socket(接口)提供了一组端到端互相通讯的接口,做为通讯的核心功能。Websocket(协议)定义了通讯中数据的封装和显示的格式,并且最大的特色是它支持服务端向客户端的主动推送,这一点是 HTTP 作不到的。而 Workerman (框架)将这二者很好地整合在了一块儿(固然不只仅于此)。GatewayWorker(框架)是在 Workerman 的基础上开发的 TCP 长链接应用框架,提供了单发、群发和广播等接口,还能够客户端和客户端通讯。数组
因此最终我选择了 GatewayWorker 做为 Socket 监听的服务端,Laravel 做为 HTTP 请求的业务处理框架,完成一个响应式的在线聊天室(项目地址在下一篇文章最后)。服务器
先理解一下工做原理,能够对 GatewayWorker 有个总体的把握。这一起其实手册里已经详细不啰嗦地解释清楚了。我这里再理一下:session
一、Register、Gateway、BusinessWorker 3 种进程依次启动(由于支持多进程,因此我说“种”,而不是“个”)app
二、Gateway 进程和 BusinessWorker 进程启动后向 Register 服务进程发起长链接注册自身。框架
三、Register 服务进程收到 Gateway 的注册后,把全部 Gateway 进程的通信地址写入内存。socket
四、Register 服务进程收到 BusinessWorker 的注册后,把内存中的 Gateway 进程通信地址发给全部 BusinessWorker 进程。分布式
五、BusinessWorker 进程收到全部 Gateway 进程的通信地址后,尝试链接 Gateway。函数
六、至此,全部 Gateway 和 BusinessWorker 进程就经过 Register 服务进程创建了长链接。
若是期间有新的 Gateway 注册到 Register(通常是分布式部署加机器),新 Gateway 的通信地址会被广播给全部 BusinessWorker,BusinessWorker 收到通知后创建新链接。
若是期间有 Gateway 下线,Register 会收到通知、删除这个 Gateway 的内部通信地址,并将新的内部通信地址列表广播给全部 BusinessWorker,BusinessWorker 再也不链接下线的 Gateway。
七、客户端的链接事件和链接上的数据会经由 Gateway 转发给 BusinessWorker,BusinessWorker 默认调用 Events.php 中 Events 类的 onConnect、onMessage、onClose 事件回调处理业务逻辑。
八、BusinessWorker 负责运行全部的业务逻辑,实际的处理逻辑默认在 Events.php 中实现。
GatewayWorker 是以进程的形式进驻内存的,了解了它的工做原理以后,有必要理解一下它的进程模型。
GatewayWorker 主要有 3 种进程:Register 进程、Gateway 进程和 BusinessWorker 进程。这 3 种进程分别对应了内核源码中的 Register 类、Gateway 类和 BusinessWorker 类,而且它们都是基于 Workerman 框架的 Worker 类开发的,因此这 3 种进程都有一些公共的属性,好比 name、count、onWorkerStart、onWorkerStop 等等。能够说,GatewayWorker 里全部的进程都是 Worker 进程。
Register 进程主要负责 Gateway 进程 与 BusinessWorker 进程创建链接并内部通信。
该进程由 Register 类实例化,并随进程启动进驻内存。
它可定制的只有实例化时指定自身所在的服务进程地址。包括 IP 和端口,而且目前只支持 text 协议。text 协议是 Workerman 框架定义的一种文本协议(协议格式为:数据包 + 换行符)。
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进程,各进程分别监听 200三、200四、200五、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 类提供的接口。
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 所在的文件。咱们在实际的开发中,只须要关注这一个文件。
Events 里有 5 个事件回调的处理方法,按照发生顺序,依次是
onConnect (string $client_id):当客户端链接上 Gateway 进程时触发(TCP 三层握手)。
onClose (string client_id):当客户端链接断开时触发。不管是客户端仍是服务端主动断开,都会触发。
这里面咱们经常使用到的是 onMessage 和 onClose 回调,其余比较少用。
上面的回调事件里有一个比较重要的参数:$client_id。client_id 是 20 个字符的定长字符串,用来全局标识一个 Socket 链接。每一个客户端链接都会被分配一个全局惟一的 client_id。客户端关闭链接时,对应的 client_id 会失效。当客户端再次打开一个 Socket 链接时,会被分配一个新的 client_id。
既然(默认)在 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/