使用场景
公司原有的业务消息推送是靠前端 ajax 轮眉请求后端接口完成的。然后我新来的,让我改成 websocket 双向通信的来做消息推送。
业务场景 => PC 端浏览器打开后台系统后,如果有业务订单,然后时时推送到 PC 端上,PC 进行时时的语音播报。
步骤
-
安装框架
-
composer create-project hyperf/hyperf-skeleton
【注意要符合框架的使用环境,在安装】 -
安装 websocket 服务端的 composer 安装包
1. `composer require hyperf/websocket-server`
-
安装 websocket 的客户端,安装的原因是 通过 http 请求后,websocket 客户端直接向 websocket 服务端建立连接,然后推送消息。
1. `composer require hyperf/websocket-client`
-
修改 config 文件中的 server.php 配置文件,有时候,配置文件不存在,需要自己手动创建。
1. server.php 配置文件代码:
-
server.php 配置文件代码【改成自己的端口号】
<?php
declare(strict\_types=1);
/\*\*
\* This file is part of Hyperf. \* \* @link https://www.hyperf.io
\* @document https://doc.hyperf.io
\* @contact group@hyperf.io
\* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
\*/
use Hyperf\\Server\\Server;
use Hyperf\\Server\\SwooleEvent;
return [
'mode' => SWOOLE_PROCESS,
'servers' => [
[ 'name' => 'http',
'type' => Server::SERVER_HTTP,
'host' => '0.0.0.0',
'port' => 8098,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
SwooleEvent::ON_REQUEST => [Hyperf\\HttpServer\\Server::class, 'onRequest'],
],
],
[
'name' => 'ws',
'type' => Server::SERVER\_WEBSOCKET,
'host' => '0.0.0.0',
'port' => 8099,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
SwooleEvent::ON_HAND_SHAKE => [Hyperf\\WebSocketServer\\Server::class, 'onHandShake'],
SwooleEvent::ON_MESSAGE => [Hyperf\\WebSocketServer\\Server::class, 'onMessage'],
SwooleEvent::ON_CLOSE => [Hyperf\\WebSocketServer\\Server::class, 'onClose'],
],
],
],
'settings' => [
'enable_coroutine' => true,
'worker_num' => swoole_cpu_num(),
'pid_file' => BASE_PATH . '/runtime/hyperf.pid',
'open_tcp_nodelay' => true,
'max_coroutine' => 100000,
'open_http2\_protocol' => true,
'max_request' => 100000,
'socket_buffer_size' => 2 * 1024 * 1024,
],
'callbacks' => [
SwooleEvent::ON_BEFORE_START => [Hyperf\\Framework\\Bootstrap\\ServerStartCallback::class, 'beforeStart'],
SwooleEvent::ON_WORKER_START => [Hyperf\\Framework\\Bootstrap\\WorkerStartCallback::class, 'onWorkerStart'],
SwooleEvent::ON_PIPE_MESSAGE => [Hyperf\\Framework\\Bootstrap\\PipeMessageCallback::class, 'onPipeMessage'],
],
];
api 和 websocket 服务端链接的路由代码,routes.php
换成自己的路由对应的控制器和方法
websocket 服务端代码 VoiceBroadcastWebSocketController.php
<?php
declare(strict\_types=1);
namespace App\\Controller\\WebSocket;
use App\\Service\\SendWebSocketQueueService;
use Hyperf\\Contract\\OnCloseInterface;
use Hyperf\\Contract\\OnMessageInterface;
use Hyperf\\Contract\\OnOpenInterface;
use Hyperf\\Validation\\ValidationException;
use Swoole\\Exception;
use Swoole\\Http\\Request;
use Swoole\\Server;
use Swoole\\Websocket\\Frame;
use Swoole\\WebSocket\\Server as WebSocketServer;
use App\\Exception\\WebSocketException;
use Hyperf\\Utils\\ApplicationContext;
use Hyperf\\Logger\\LoggerFactory;
use Hyperf\\Di\\Annotation\\Inject;
class VoiceBroadcastWebSocketController implements OnMessageInterface, OnOpenInterface, OnCloseInterface
{
protected $redis;
/**
* @Inject
* @var SendWebSocketQueueService
*/
protected $service;
/**
* @var \\Psr\\Log\\LoggerInterface
*/
protected $logger;
// 初始化
public function __construct(LoggerFactory $loggerFactory)
{
$container = ApplicationContext::getContainer();
$this->redis = $container->get(\Redis::class);
$this->logger = $loggerFactory->get('log','default');
}
// onmessage 方法接收 客户端或者服务端消息
public function onMessage(WebSocketServer $server, Frame $frame): void
{
$recvData = json_decode($frame->data);
if(!is_object($recvData)) {
$this->checkData($frame->data,$frame->fd);
} else {
$this->sendData($server,$frame->data,$frame->fd);
}
}
/**
* 校验数据
* @param $string
* @param $fd
* @return string
*/
function checkData ($string, $fd) {
if (!is_string($string)) {
$this->logger->error('字符串类型错误');
return '字符串类型错误';
}
$strArray = explode('_',$string);
$shopIds = json_decode($string[1],true);
if(!is_array($shopIds) || empty($shopIds)) {
$this->logger->error('参数错误');
return '参数有误';
}
echo "全部映射成功"
}
/**
* 发送消息到 PC 端
*/
public function sendData($server,$sendData,$fd) {
$recvData = json_encode($sendData,true);
$uid = $recvData['uid'];
$data = $recvData['data'];
$fdsArr = $this->redis->sMembers('jiayouwa:websocket:voiceSet_'.$uid);
echo 'voiceSet_'.$uid;
$data = [
'result' => true,
'code'=>0,
'msg'=>'操作成功',
'data'=>$data,
];
if(count($fdsArr)) {
foreach ($fdsArr as $key=>$value) {
try {
$server->push(intval($value),json_encode($data));
echo "线程:$fd 向线程 $value 发送信息\\n";
} catch (\\Throwable $e) {
// 增加 重试次数
$this->service->push($recvData,1);
// 把数据删除
$this->redis->sRem('jiayouwa:websocket:voiceSet_'.$uid,$value);
continue;
}
}}
}
public function onClose(Server $server, int $fd, int $reactorId): void
{
echo "$fd\-closed\\n";
}
public function onOpen(WebSocketServer $server, Request $request): void
{
echo "线程:$request\->fd\-打开\\n";
}
}
http 客户端链接到 websocket 文件 VoiceBroadcastController.php
/**
* 向指定浏览器发送数据
*
* @param VoiceBroadcastRequest $request
* @param ResponseInterface $response
* @return \\Psr\\Http\\Message\\ResponseInterface
*/
public function voiceBroadcast(VoiceBroadcastRequest $request, ResponseInterface $response)
{
$serviceType = $request->input('service_type',0);
$name = $request->input('name','');
$shopNameId = $request->input('shop_name_id',0);
// 判断是在redis 中存在
$fdsArr = $this->redis->sMembers('jiayouwa:websocket:voiceSet_'.$shopNameId);
if(!count($fdsArr)) {
return $this->returnSuccess([],true,0,'shop_name_id 不存在');
}
$data = [
'uid'=>$shopNameId,
'service'=>'voiceBroadcast',
'data'=> array(
array(
'service_type'=>$serviceType,
'name'=>$name,
),
),
];
// 发送数据到 webSocket
$this->connectWebSocket($data);
return $this->returnSuccess();
}
/**
* 链接websocket 并发送数据
* @param $data
* @return array
*/
public function connectWebSocket($data) {
$client = $this->clientFactory->create($this->webSocketIp);
$client->push(json_encode($data));
}
pc 端请求 websocket 代码
<script>
//这里的ip地址改为自己服务器的ip地址
var ws = new WebSocket('ws://192.168.0.0:9502/voiceBroadcast');
ws.onopen = function(){
var uid = 'jiayouwa_[1,2,3]';
ws.send(uid);
};
ws.onmessage = function(e){
var message_info =e.data
console.log(message_info);
};
</script>
注意
用 hyperf 框架实现太简单了。只需下载 websocket 的客户端 和 服务端 composer 包。
如果要实现点对点的 消息推送,只需要将你的 uid 和 fd 进程 redis 的 key=> value 的映射即可
如果要实现 一个推送到 多个 pc 端,将 uid => [1,2,3,4,5] 用 redis 的集合类型就好
代码仅供参考,有什么问题评论就好
写文章今年才开始,写的不好,请多多包涵
hyperf 框架地址:https://doc.hyperf.io/#/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。