1.安装workman扩展
composer require workerman/workerman
2.安装think-worker
composer require topthink/think-worker=1.0.*
3.如果在windows下使用,还需下载
composer require workerman/workerman-for-win
4.根目录创建server.php 。用来启动服务
#!/usr/bin/env php
<?php
define('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','push/Worker');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';
5.新建Worker.php 服务器要记得开发端口
<?php
namespace app\push\controller;
use think\worker\Server;
use Workerman\Lib\Timer;
use think\Log;
class Worker extends Server
{
protected $socket = 'websocket://127.0.0.1:2346';
protected $uidConnections = [];
protected $HEARTBEAT_TIME = '300';
public function _initialize()
{
Log::init(['type' => 'File','apart_level' => ['API'], 'path' => '../runtime/log/']);
}
/**
* 收到信息
* @param $connection
* @param $data
*/
public function onMessage($connection, $data)
{
// 给connection临时设置一个lastMessageTime属性,用来记录上次收到消息的时间
$connection->lastMessageTime = time();
$data = json_decode($data,false);
//接收到消息进行逻辑处理 修改状态
switch($data->type){
case 'login':
// 保存该用户的输送数据
$this->uidConnections[$data->uid] = $connection;
$connection->send('所有用户等会收到的信息');
$this->send_uid($data->uid,"通过保存uid:{$data->uid}为给你发的信息");
break;
case 'ping':
// 心跳
$connection->send('pong');
break;
case 'sendMessage':
// 发送所有消息
$this->send_all($data->message,$data->uid);
break;
case 'sendUser' :
// 发送单个消息
$this->send_uid($data->uid,$data->message);
break;
}
}
/**
* 当连接建立时触发的回调函数
* @param $connection
*/
public function onConnect($connection)
{
$connection->send('已建立连接');
}
/**
* 当连接断开时触发的回调函数
* @param $connection
*/
public function onClose($connection)
{
$connection->send('连接断开');
}
/**
* 当客户端的连接上发生错误时触发
* @param $connection
* @param $code
* @param $msg
*/
public function onError($connection, $code, $msg)
{
echo "error $code $msg\n";
}
/**
* 每个进程启动
* @param $worker
*/
public function onWorkerStart($worker)
{
// 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
$inner_text_worker = new \Workerman\Worker('Text://127.0.0.1:5678');
$inner_text_worker->onMessage = function ($connection, $buffer){
$buffer = json_decode($buffer,false);
switch($buffer->type){
case 'sendMessage' :
$res = $this->send_all($buffer->message);
break;
case 'sendUser' :
$res = $this->send_uid($buffer->uid,$buffer->message);
break;
default:
$res = $this->send_all('1111');
}
$connection->send($res ? 'ok' : 'fail');
};
$inner_text_worker->listen();
Timer::add($this->HEARTBEAT_TIME, function()use($worker){
$time_now = time();
foreach($worker->connections as $connection) {
// 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
$diff_time = $time_now - $connection->lastMessageTime;
$msg = '距离上次通话已经过去'.$diff_time.'秒';
$connection->send($msg);
// 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
if ($time_now - $connection->lastMessageTime > $this->HEARTBEAT_TIME) {
$connection->close();
}
}
});
}
public function send_uid($uid,$message)
{
if(isset($this->uidConnections[$uid])){
// 获取之前用户的链接
$conn = $this->uidConnections[$uid];
//echo $uid.PHP_EOL;
$conn->send($message);
Log::write("消息发送给 UID $uid: $message", 'API'); // 添加调试日志
return true;
}
Log::write("UID $uid 不存在", 'API'); // 添加调试日志
return false;
}
public function send_all($message,$uid = '')
{
foreach($this->uidConnections as $conn){
if(!empty($uid)){
//不推送给自己
if($conn != $this->uidConnections[$uid]){
$conn->send($message);
}
}else{
$conn->send($message);
}
}
return true;
}
}
6.根目录下执行命令,运行服务
php server.php start
7.新加test.html 用来接受测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
// 创建一个Socket实例
var socket = new WebSocket('ws://127.0.0.1:2346');
var uid = "33";
//心跳间隔
var heartbeatInterval = 290 * 1000;
// 打开Socket
socket.onopen = function(event) {
// 发送一个初始化消息
socket.send(
JSON.stringify({
type: "login",
uid: uid,
})
);
// 启动心跳定时器
startHeartbeat();
};
socket.onmessage = function(event) {
console.log('收到消息',event.data);
// 处理收到的心跳回复
if (event.data === 'pong') {
console.log('心跳回复');
}
};
// 监听Socket的关闭
socket.onclose = function(event) {
console.log('关闭监听',event);
// 连接关闭后,清除心跳定时器
stopHeartbeat();
};
// 心跳定时器
var heartbeatTimer;
function startHeartbeat() {
heartbeatTimer = setInterval(function() {
socket.send(JSON.stringify({ type: 'ping' }));
console.log('发送心跳');
}, heartbeatInterval);
}
function stopHeartbeat() {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
}
function send()
{
var val = $("#msg").val();
if(val==''){
alert('请输入发送内容');
return false;
}
socket.send(JSON.stringify({
type: "sendMessage",
uid: uid,
message:val
}));
}
</script>
</head>
<body>
<input type="text" id="msg">
<button onclick="send()">发送消息</button>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</html>
8.手动推送消息
public static function push_msg($str = '',$uid = '33' )
{
// $uid = input('uid','33');
$type = input('type','sendUser');
$msg = input('msg','hello word');
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
if (!$client) {
echo "Error: $errmsg\n";
return false;
}
// 推送的数据,包含用户,表示是给这个用户推送
//有数据则进行推送
if($str)
{
$data = array('uid'=>$uid, 'message'=>$str,'type'=>$type);
// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
$bytesWritten = fwrite($client, json_encode($data) . "\n");
if ($bytesWritten === false) {
Log::write('Failed to write data to the socket', 'API');
fclose($client);
return false;
}
stream_set_timeout($client, 1);
// 读取推送结果
$response = fread($client, 8192);
fclose($client);
return $response;
}
}