2种方法教你,如何将exe注册为windows服务,直接从后台运行 - 知乎

mikel阅读(889)

来源: 2种方法教你,如何将exe注册为windows服务,直接从后台运行 – 知乎

方法一:使用windows自带的命令sc

首先我们要打开cmd,下面的命令在cmd中运行,最好使用管理员运行cmd

注册服务:

sc create ceshi binpath= D:\ceshi\ceshi.exe type= own start= auto displayname= ceshi

binpath:你的应用程序所在的路径。

displayname:服务显示的名称

如何判断服务是否注册成功:

在cmd中输入services.msc打开系统服务,查看是否出现ceshi名称的服务(即displayname=后面的参数,我这里是ceshi

or

按下面的方式尝试启动服务

启动服务

net start ceshi

停止服务

net stop ceshi

删除服务

sc delete "ceshi"

方法二:使用instsrv+srvany

使用方法一,如果你的exe不符合服务的规范,启动有可能会失败

这种情况下,我们使用instsrv+srvany

什么是instsrv+srvany

instsrv.exe.exe和srvany.exe是Microsoft Windows Resource Kits工具集中 的两个实用工具,这两个工具配合使用可以将任何的exe应用程序作为window服务运行。

srany.exe是注册程序的服务外壳,可以通过它让应用程序以system账号启动,可以使应用程序作为windows的服务随机器启动而自动启动,从而隐藏不必要的窗口

下载:

链接:pan.baidu.com/s/1gKu_Ww 提取码:s1vm

window64位系统

安装

  1. 将instsrv.exe和srvany.exe拷贝到C:\WINDOWS\SysWOW64目录下
  2. 打开cmd
  3. 运行命令:instsrv MyService C:\WINDOWS\SysWOW64\srvany.exe

注意:Myservice是自定义的服务的名称,可以根据应用程序名称任意更改

运行成功!

配置

  1. 打开注册表:(cmd中输入:regedit
  2. ctrl+F,搜索Myservice(之前自定义的服务名称)
  3. 右击Myservice新建项,名称为Parameters
  4. 之后在Parameters中新建几个字符串值
  • 名称 Application 值:你要作为服务运行的程序地址。
  • 名称 AppDirectory 值:你要作为服务运行的程序所在文件夹路径。
  • 名称 AppParameters 值:你要作为服务运行的程序启动所需要的参数。

之后启动服务Myservice即可后台运行exe!

window32位系统

安装

  1. 将instsrv.exe和srvany.exe拷贝到C:\WINDOWS\system32目录下
  2. 打开cmd
  3. 运行命令:instsrv MyService C:\WINDOWS\system32\srvany.exe

注意:Myservice是自定义的服务的名称,可以根据应用程序名称任意更改

运行成功!

我这里是64位。

配置

  1. 打开注册表:(cmd中输入:regedit)
  2. ctrl+F,搜索Myservice(之前自定义的服务名称)
  3. 右击Myservice新建项,名称为Parameters
  4. 之后在Parameters中新建几个字符串值
  • 名称 Application 值:你要作为服务运行的程序地址。
  • 名称 AppDirectory 值:你要作为服务运行的程序所在文件夹路径。
  • 名称 AppParameters 值:你要作为服务运行的程序启动所需要的参数。

之后启动服务Myservice即可后台运行exe!

gatewayworker在events的onWorkerStart回调中如何获取到worker进程,避免多进程多个定时器并发启动的问题

mikel阅读(718)

问题代码

class Events
{
public static function onWorkerStart()
{

        Timer::add(1, function(){
            //echo "timer:".time()."\n";
            list($msec, $sec) = explode(' ', microtime());
            $msectime = (float) sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
            $time=array();
            $time['time']=$msectime;
            $message = array();
            $message["code"]=201;//定时器
            $message["message"]='time '.$msectime;
            $message["data"]=$time;
            Gateway::sendToAll(json_encode($message));
        });
}
这个定时器代码每秒群发消息给客户端进行倒计时,当多进程的时候每个进程都会启动一个定时器来群发消息,在客户端访问不频繁的情况下,不会出现阻塞的情况,当客户端和服务器间频繁发送消息的时候,客户端一次接受所有进程的消息,导致处理不过来卡顿的问题会很严重,其实只需要一个进程启动定时器,群发消息即可于是对进程id进行了判断,修改过的代码如下:
class Events
{
    public static function onWorkerStart($worker)
    {
        
        //Gateway::sendToAll('workerid'.$businessWorker->id);
        if($worker->id === 0){
            Timer::add(1, function(){
                //echo "timer:".time()."\n";
    			list($msec, $sec) = explode(' ', microtime());
    			$msectime = (float) sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
    			$time=array();
    			$time['time']=$msectime;
    			$message = array();
    			$message["code"]=201;//定时器
    			$message["message"]='time '.$msectime;
    			$message["data"]=$time;
    			Gateway::sendToAll(json_encode($message));
            });
        }
    }

参考文章:https://www.workerman.net/q/4568

深入理解GatewayWorker框架 - 简书

mikel阅读(943)

来源: 深入理解GatewayWorker框架 – 简书

序言

本文只是结合GatewayWorker和Workerman的官方文档和源码,深入了解执行过程。以便更深入的了解并使用

GatewayWorker基于Workerman开发的一个项目框架。Register进程负责保存Gateway进程和BusinessWorker进程的地址,建立两者的连接。Gateway进程负责维持客户端连接,并转发客户端的数据给BusinessWorker进程处理,BusinessWorker进程负责处理实际的业务逻辑(默认调用Events.php处理业务),并将结果推送给对应的客户端。Register、Gateway、BusinessWorker进程都是继承Worker类实现各自的功能,所以了解GatewayWorker框架的内部执行过程,需要优先理解Worker的内容

GatewayWorker目录结构

├── Applications // 这里是所有开发者应用项目
│   └── YourApp  // 其中一个项目目录,目录名可以自定义
│       ├── Events.php // 开发者只需要关注这个文件
│       ├── start_gateway.php // gateway进程启动脚本,包括端口        号等设置
│       ├── start_businessworker.php // businessWorker进程启动  脚本
│       └── start_register.php // 注册服务启动脚本
│
├── start.php // 全局启动脚本,此脚本会依次加载Applications/项目/start_*.php启动脚本
│
└── vendor    // GatewayWorker框架和Workerman框架源码目  录,此目录开发者不用关心

start.php 为启动脚本,在该脚本中,统一加载start_gateway.php start_businessworker.php start_register.php进程脚本,最后通过Worker::runAll();运行所有服务。

工作原理

1、Register、Gateway、BusinessWorker进程启动
2、Gateway、BusinessWorker进程启动后向Register服务进程发起长连接注册自己
3、Register服务收到Gateway的注册后,把所有Gateway的通讯地址保存在内存中
4、Register服务收到BusinessWorker的注册后,把内存中所有的Gateway的通讯地址发给BusinessWorker
5、BusinessWorker进程得到所有的Gateway内部通讯地址后尝试连接Gateway
6、如果运行过程中有新的Gateway服务注册到Register(一般是分布式部署加机器),则将新的Gateway内部通讯地址列表将广播给所有BusinessWorker,BusinessWorker收到后建立连接
7 、如果有Gateway下线,则Register服务会收到通知,会将对应的内部通讯地址删除,然后广播新的内部通讯地址列表给所有BusinessWorker,BusinessWorker不再连接下线的Gateway
8、至此Gateway与BusinessWorker通过Register已经建立起长连接
9、客户端的事件及数据全部由Gateway转发给BusinessWorker处理,BusinessWorker默认调用Events.php中的onConnect onMessage onClose处理业务逻辑。
10、BusinessWorker的业务逻辑入口全部在Events.php中,包括onWorkerStart进程启动事件(进程事件)、onConnect连接事件(客户端事件)、onMessage消息事件(客户端事件)、onClose连接关闭事件(客户端事件)、onWorkerStop进程退出事件(进程事件)

1 Register、Gateway、BusinessWorker进程启动

项目根目录下的start.php 为启动脚本,在该脚本中,加载start_gateway.php start_businessworker.php start_register.php进程脚本,完成各个服务的Worker初始化:

// 加载所有Applications/*/start.php,以便启动所有服务
foreach(glob(__DIR__.'/Applications/*/start*.php') as $start_file)
{
    require_once $start_file;
}

最后通过Worker::runAll();运行所有服务。

// 运行所有服务
Worker::runAll();
运行所有服务,先看一遍runAll()方法的执行内容

public static function runAll()
{
    // 检查运行环境
    self::checkSapiEnv();
    //初始化环境变量
    self::init();
    // 解析命令
    self::parseCommand();
    // 尝试以守护进程模式运行
    self::daemonize();
    // 初始化所有worker实例,主要是监听端口
    self::initWorkers();
    // 初始化所有信号处理函数
    self::installSignal();
    // 保存主进程pid
    self::saveMasterPid();
    // 展示启动界面
    self::displayUI();
    // 创建子进程(worker进程),然后给每个子进程绑定loop循环监听事件tcp
    self::forkWorkers();
    // 尝试重定向标准输入输出
    self::resetStd();
    // 监控所有子进程(worker进程)
    self::monitorWorkers();
}

self::init()初始化环境变量中,有以下部分代码,保存$_idMap从PID映射到工作进程ID

// Init data for worker id.
   self::initId();
protected static function initId()
 {
   foreach (self::$_workers as $worker_id => $worker) {
       $new_id_map = array();
       for($key = 0; $key < $worker->count; $key++) {
          $new_id_map[$key] = isset(self::$_idMap[$worker_id]      [$key]) ? self::$_idMap[$worker_id][$key] : 0;
       }
       self::$_idMap[$worker_id] = $new_id_map;
   }
 }

self::forkWorkers()方法通过循环self::$_workers数组,fork各自worker的count数量的进程。然后通过调用

$worker->run();

运行当前worker实例,在run方法中通过

  if (!self::$globalEvent) {
       $event_loop_class = self::getEventLoopName();
      self::$globalEvent = new $event_loop_class;
      // Register a listener to be notified when server socket is ready to read.
       if ($this->_socketName) {
           if ($this->transport !== 'udp') {
               self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ,
                   array($this, 'acceptConnection'));
           } else {
               self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ,
                   array($this, 'acceptUdpConnection'));
           }
       }

获取一个当前可用的事件轮询方式,然后根据当前的协议类型添加一个监听到事件轮询中
然后,尝试出发当前进程模型的onWorkerStart回调,此回调会在Gateway类以及BusinessWorker类中都会定义,代码

 if ($this->onWorkerStart) {
       try {
           call_user_func($this->onWorkerStart, $this);
       } catch (\Exception $e) {
           self::log($e);
           // Avoid rapid infinite loop exit.
           sleep(1);
           exit(250);
       } catch (\Error $e) {
           self::log($e);
           // Avoid rapid infinite loop exit.
           sleep(1);
           exit(250);
       }
   }

最后,执行事件的循环等待socket事件,处理读写等操作,代码

 // Main loop.
   self::$globalEvent->loop();

以上是runAll()方法的部分内容,会在了解GatewayWorker的工作原理的时候用到

2.1 Gateway进程向Register服务进程发起长连接注册自己

初始化Gateway

$gateway = new Gateway("text://0.0.0.0:8383");

在Gateway类中重写run方法,当调用runAll()方法启动进程时,fork进程之后,运行worker实例的时候,会调用到此重写的run方法

public function run()
{
    // 保存用户的回调,当对应的事件发生时触发
    $this->_onWorkerStart = $this->onWorkerStart;
    $this->onWorkerStart  = array($this, 'onWorkerStart');
    // 保存用户的回调,当对应的事件发生时触发
    $this->_onConnect = $this->onConnect;
    $this->onConnect  = array($this, 'onClientConnect');
    // onMessage禁止用户设置回调
    $this->onMessage = array($this, 'onClientMessage');
    // 保存用户的回调,当对应的事件发生时触发
    $this->_onClose = $this->onClose;
    $this->onClose  = array($this, 'onClientClose');
    // 保存用户的回调,当对应的事件发生时触发
    $this->_onWorkerStop = $this->onWorkerStop;
    $this->onWorkerStop  = array($this, 'onWorkerStop');
    $this->_startTime = time();
    // 运行父方法
    parent::run();
}

定义了$this->onWorkerStart回调,

$this->onWorkerStart  = array($this, 'onWorkerStart');

 

 

执行到Worker类中的run()方法时,被触发。即,上边提到的

Worker脚本中的run方法

调用Gateway类中的onWorkerStart方法,代码

public function onWorkerStart()
{
    $this->lanPort = $this->startPort + $this->id;
    if ($this->pingInterval > 0) {
        $timer_interval = $this->pingNotResponseLimit > 0 ? $this->pingInterval / 2 : $this->pingInterval;
        Timer::add($timer_interval, array($this, 'ping'));
    }
    if ($this->lanIp !== '127.0.0.1') {
        Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingBusinessWorker'));
    }
    if (strpos($this->registerAddress, '127.0.0.1') !== 0) {
        Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingRegister'));
    }
    if (!class_exists('\Protocols\GatewayProtocol')) {
        class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol');
    }
    // 初始化 gateway 内部的监听,用于监听 worker 的连接已经连接上发来的数据
    $this->_innerTcpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}");
    $this->_innerTcpWorker->listen();
    // 重新设置自动加载根目录
    Autoloader::setRootPath($this->_autoloadRootPath);
    // 设置内部监听的相关回调
    $this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage');
    $this->_innerTcpWorker->onConnect = array($this, 'onWorkerConnect');
    $this->_innerTcpWorker->onClose   = array($this, 'onWorkerClose');
    // 注册 gateway 的内部通讯地址,worker 去连这个地址,以便 gateway 与 worker 之间建立起 TCP 长连接
    $this->registerAddress();
    if ($this->_onWorkerStart) {
        call_user_func($this->_onWorkerStart, $this);
    }
}

$this->startPort : 内部通讯起始端口,假如$gateway->count=4,起始端口为4000,可在gateway启动脚本中自定义
$this->id : 基于worker实例分配的进程编号,当前从0开始,根据count自增。在fork进程的时候生成

Worker.php

$this->_innerTcpWorker:用于监听 worker 的连接已经连接上发来的数据。在工作原理5中,BusinessWorker进程得到所有的Gateway内部通讯地址后尝试连接Gateway以及其他两者之间的通信(连接,消息,关闭)会被调用
$this->registerAddress(): 代码中$this->registerAddress是在start_gateway.php初始化Gateway类之后定义的。该端口是Register进程所监听。此处异步的向Register进程发送数据,存储当前 Gateway 的内部通信地址 

public function registerAddress()
{
    $address                   = $this->lanIp . ':' . $this->lanPort;
    $this->_registerConnection = new AsyncTcpConnection("text://{$this->registerAddress}");
    $this->_registerConnection->send('{"event":"gateway_connect", "address":"' . $address . '", "secret_key":"' . $this->secretKey . '"}');
    $this->_registerConnection->onClose = array($this, 'onRegisterConnectionClose');
    $this->_registerConnection->connect();
}

$this->lanIp: Gateway所在服务器的内网IP

2.2 BusinessWorker进程向Register服务进程发起长连接注册自己

BusinessWorker类中同样重写run方法,定义了$this->onWorkerStart

 public function run()
 {
    $this->_onWorkerStart  = $this->onWorkerStart;
    $this->_onWorkerReload = $this->onWorkerReload;
    $this->_onWorkerStop = $this->onWorkerStop;
    $this->onWorkerStop   = array($this, 'onWorkerStop');
    $this->onWorkerStart   = array($this, 'onWorkerStart');
    $this->onWorkerReload  = array($this, 'onWorkerReload');
    parent::run();
 }

执行Worker类中的run方法,触发BusinessWorker中的onWorkerStart

protected function onWorkerStart()
{
    if (!class_exists('\Protocols\GatewayProtocol')) {
        class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol');
    }
    $this->connectToRegister();
    \GatewayWorker\Lib\Gateway::setBusinessWorker($this);
    \GatewayWorker\Lib\Gateway::$secretKey = $this->secretKey;
    if ($this->_onWorkerStart) {
        call_user_func($this->_onWorkerStart, $this);
    }
    
    if (is_callable($this->eventHandler . '::onWorkerStart')) {
        call_user_func($this->eventHandler . '::onWorkerStart', $this);
    }

    if (function_exists('pcntl_signal')) {
        // 业务超时信号处理
        pcntl_signal(SIGALRM, array($this, 'timeoutHandler'), false);
    } else {
        $this->processTimeout = 0;
    }

    // 设置回调
    if (is_callable($this->eventHandler . '::onConnect')) {
        $this->_eventOnConnect = $this->eventHandler . '::onConnect';
    }

    if (is_callable($this->eventHandler . '::onMessage')) {
        $this->_eventOnMessage = $this->eventHandler . '::onMessage';
    } else {
        echo "Waring: {$this->eventHandler}::onMessage is not callable\n";
    }

    if (is_callable($this->eventHandler . '::onClose')) {
        $this->_eventOnClose = $this->eventHandler . '::onClose';
    }

    // 如果Register服务器不在本地服务器,则需要保持心跳
    if (strpos($this->registerAddress, '127.0.0.1') !== 0) {
        Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingRegister'));
    }
}

通过connectToRegister方法,发送数据到Register进程,连接服务注册中心

public function connectToRegister()
{
    $this->_registerConnection = new AsyncTcpConnection("text://{$this->registerAddress}");
    $this->_registerConnection->send('{"event":"worker_connect","secret_key":"' . $this->secretKey . '"}');
    $this->_registerConnection->onClose   = array($this, 'onRegisterConnectionClose');
    $this->_registerConnection->onMessage = array($this, 'onRegisterConnectionMessage');
    $this->_registerConnection->connect();
}

3 Register服务收到Gateway的注册后,把所有的Gateway的通讯地址保存在内存中

在Register类中,重写了run方法,定义了当前的

     $this->onConnect = array($this, 'onConnect');
    // 设置 onMessage 回调
    $this->onMessage = array($this, 'onMessage');

    // 设置 onClose 回调
    $this->onClose = array($this, 'onClose');

三个属性,当Register启动的进程收到消息时,会触发onMessage方法

 public function onMessage($connection, $buffer)
{
    // 删除定时器
    Timer::del($connection->timeout_timerid);
    $data       = @json_decode($buffer, true);
    if (empty($data['event'])) {
        $error = "Bad request for Register service. Request info(IP:".$connection->getRemoteIp().", Request Buffer:$buffer). See http://wiki.workerman.net/Error4 for detail";
        Worker::log($error);
        return $connection->close($error);
    }
    $event      = $data['event'];
    $secret_key = isset($data['secret_key']) ? $data['secret_key'] : '';
    // 开始验证
    switch ($event) {
        // 是 gateway 连接
        case 'gateway_connect':
            if (empty($data['address'])) {
                echo "address not found\n";
                return $connection->close();
            }
            if ($secret_key !== $this->secretKey) {
                Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true));
                return $connection->close();
            }
            $this->_gatewayConnections[$connection->id] = $data['address'];
            $this->broadcastAddresses();
            break;
        // 是 worker 连接
        case 'worker_connect':
            if ($secret_key !== $this->secretKey) {
                Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true));
                return $connection->close();
            }
            $this->_workerConnections[$connection->id] = $connection;
            $this->broadcastAddresses($connection);
            break;
        case 'ping':
            break;
        default:
            Worker::log("Register unknown event:$event IP: ".$connection->getRemoteIp()." Buffer:$buffer. See http://wiki.workerman.net/Error4 for detail");
            $connection->close();
    }
}

当$event = ‘gateway_connect’时,是Gateway发来的注册消息,保存到$this->_gatewayConnections数组中,在通过broadcastAddresses方法将当前$this->_gatewayConnections中所有的Gatewat通讯地址转发给所有BusinessWorker进程

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

 

 

同第3步中,Register类收到BusinessWorker的注册时,会触发onMessage方法中的worker_connect,case选项。

image.png

同时,将当前worker连接加入到$_workerConnections数组中,在通过broadcastAddresses方法将当前$this->_gatewayConnections中所有的Gatewat通讯地址转发给所有BusinessWorker进程。

5 BusinessWorker进程得到所有的Gateway内部通讯地址后尝试连接Gateway

在BusinessWoker类的启动中,通过重写run方法,定义的启动onWorkerStart方法中,通过connectToRegister方法注册服务中心的同时,也定义了onMessage匿名函数,用于接收消息回调。

$this->_registerConnection->onMessage = array($this, 'onRegisterConnectionMessage');

即,当注册中心发来消息时候,回调到此处

 public function onRegisterConnectionMessage($register_connection, $data)
{
    $data = json_decode($data, true);
    if (!isset($data['event'])) {
        echo "Received bad data from Register\n";
        return;
    }
    $event = $data['event'];
    switch ($event) {
        case 'broadcast_addresses':
            if (!is_array($data['addresses'])) {
                echo "Received bad data from Register. Addresses empty\n";
                return;
            }
            $addresses               = $data['addresses'];
            $this->_gatewayAddresses = array();
            foreach ($addresses as $addr) {
                $this->_gatewayAddresses[$addr] = $addr;
            }
            $this->checkGatewayConnections($addresses);
            break;
        default:
            echo "Receive bad event:$event from Register.\n";
    }
}

其中Register类发来的数据是

$data   = array(
        'event'     => 'broadcast_addresses',
        'addresses' => array_unique(array_values($this->_gatewayConnections)),
    );

这个时候,就会通过checkGatewayConnections方法检查gateway的这些通信端口是否都已经连接,在通过tryToConnectGateway方法尝试连接gateway的这些内部通信地址

6 Gateway进程收到BusinessWorker进程的连接消息

同样,在Gateway进程启动的时候,触发的onWorkerStart方法中,也定义了一个内部通讯的onWorkerMessage

$this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage');

由此来接收BusinessWorker进程发来的连接消息,部分代码

public function onWorkerMessage($connection, $data)
{
    $cmd = $data['cmd'];
    if (empty($connection->authorized) && $cmd !== GatewayProtocol::CMD_WORKER_CONNECT && $cmd !== GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT) {
        self::log("Unauthorized request from " . $connection->getRemoteIp() . ":" . $connection->getRemotePort());
        return $connection->close();
    }
    switch ($cmd) {
        // BusinessWorker连接Gateway
        case GatewayProtocol::CMD_WORKER_CONNECT:
            $worker_info = json_decode($data['body'], true);
            if ($worker_info['secret_key'] !== $this->secretKey) {
                self::log("Gateway: Worker key does not match ".var_export($this->secretKey, true)." !== ". var_export($this->secretKey));
                return $connection->close();
            }
            $key = $connection->getRemoteIp() . ':' . $worker_info['worker_key'];
            // 在一台服务器上businessWorker->name不能相同
            if (isset($this->_workerConnections[$key])) {
                self::log("Gateway: Worker->name conflict. Key:{$key}");
        $connection->close();
                return;
            }
    $connection->key = $key;
            $this->_workerConnections[$key] = $connection;
            $connection->authorized = true;
            return;
        // GatewayClient连接Gateway

将worker的进程连接保存到$this->_workerConnections[$key] = $connection;

7 Gateway进程收到客户端的连接,消息时,会通过Gateway转发给worker处理

 // Gateway类的run方法中定义此属性
 $this->onMessage = array($this, 'onClientMessage');
 
 // 收到客户端消息的时候出发此函数
 public function onClientMessage($connection, $data)
 {
    $connection->pingNotResponseCount = -1;
    $this->sendToWorker(GatewayProtocol::CMD_ON_MESSAGE, $connection, $data);
 }

在sendToWorker方法中,将数据发给worker进程处理

作者:AntFoot
链接:https://www.jianshu.com/p/47a9f2f0c18b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

深入理解GatewayWorker框架_houzhyan-博客-CSDN博客_gatewayworker

mikel阅读(631)

$this->id

来源: 深入理解GatewayWorker框架_houzhyan-博客-CSDN博客_gatewayworker

原文地址:http://www.php-master.com/post/342621.html

序言

本文只是结合GatewayWorker和Workerman的官方文档和源码,深入了解执行过程。以便更深入的了解并使用

GatewayWorker基于Workerman开发的一个项目框架。Register进程负责保存Gateway进程和BusinessWorker进程的地址,建立两者的连接。Gateway进程负责维持客户端连接,并转发客户端的数据给BusinessWorker进程处理,BusinessWorker进程负责处理实际的业务逻辑(默认调用Events.php处理业务),并将结果推送给对应的客户端。Register、Gateway、BusinessWorker进程都是继承Worker类实现各自的功能,所以了解GatewayWorker框架的内部执行过程,需要优先理解Worker的内容

GatewayWorker目录结构

  1. ├── Applications // 这里是所有开发者应用项目
  2. │ └── YourApp // 其中一个项目目录,目录名可以自定义
  3. │ ├── Events.php // 开发者只需要关注这个文件
  4. │ ├── start_gateway.php // gateway进程启动脚本,包括端口 号等设置
  5. │ ├── start_businessworker.php // businessWorker进程启动 脚本
  6. │ └── start_register.php // 注册服务启动脚本
  7. ├── start.php // 全局启动脚本,此脚本会依次加载Applications/项目/start_*.php启动脚本
  8. └── vendor // GatewayWorker框架和Workerman框架源码目 录,此目录开发者不用关心

start.php 为启动脚本,在该脚本中,统一加载start_gateway.php start_businessworker.php start_register.php进程脚本,最后通过Worker::runAll();运行所有服务。

工作原理

  1. 1、Register、Gateway、BusinessWorker进程启动
  2. 2、Gateway、BusinessWorker进程启动后向Register服务进程发起长连接注册自己
  3. 3、Register服务收到Gateway的注册后,把所有Gateway的通讯地址保存在内存中
  4. 4、Register服务收到BusinessWorker的注册后,把内存中所有的Gateway的通讯地址发给BusinessWorker
  5. 5、BusinessWorker进程得到所有的Gateway内部通讯地址后尝试连接Gateway
  6. 6、如果运行过程中有新的Gateway服务注册到Register(一般是分布式部署加机器),则将新的Gateway内部通讯地址列表将广播给所有BusinessWorker,BusinessWorker收到后建立连接
  7. 7 、如果有Gateway下线,则Register服务会收到通知,会将对应的内部通讯地址删除,然后广播新的内部通讯地址列表给所有BusinessWorker,BusinessWorker不再连接下线的Gateway
  8. 8、至此Gateway与BusinessWorker通过Register已经建立起长连接
  9. 9、客户端的事件及数据全部由Gateway转发给BusinessWorker处理,BusinessWorker默认调用Events.php中的onConnect onMessage onClose处理业务逻辑。
  10. 10、BusinessWorker的业务逻辑入口全部在Events.php中,包括onWorkerStart进程启动事件(进程事件)、onConnect连接事件(客户端事件)、onMessage消息事件(客户端事件)、onClose连接关闭事件(客户端事件)、onWorkerStop进程退出事件(进程事件)

1 Register、Gateway、BusinessWorker进程启动

项目根目录下的start.php 为启动脚本,在该脚本中,加载start_gateway.php start_businessworker.php start_register.php进程脚本,完成各个服务的Worker初始化:

  1. // 加载所有Applications/*/start.php,以便启动所有服务
  2. foreach(glob(__DIR__.‘/Applications/*/start*.php’) as $start_file)
  3. {
  4. require_once $start_file;
  5. }

最后通过Worker::runAll();运行所有服务。

  1. // 运行所有服务
  2. Worker::runAll();

运行所有服务,先看一遍runAll()方法的执行内容

  1. public static function runAll()
  2. {
  3. // 检查运行环境
  4. self::checkSapiEnv();
  5. //初始化环境变量
  6. self::init();
  7. // 解析命令
  8. self::parseCommand();
  9. // 尝试以守护进程模式运行
  10. self::daemonize();
  11. // 初始化所有worker实例,主要是监听端口
  12. self::initWorkers();
  13. // 初始化所有信号处理函数
  14. self::installSignal();
  15. // 保存主进程pid
  16. self::saveMasterPid();
  17. // 展示启动界面
  18. self::displayUI();
  19. // 创建子进程(worker进程),然后给每个子进程绑定loop循环监听事件tcp
  20. self::forkWorkers();
  21. // 尝试重定向标准输入输出
  22. self::resetStd();
  23. // 监控所有子进程(worker进程)
  24. self::monitorWorkers();
  25. }

self::init()初始化环境变量中,有以下部分代码,保存$_idMap从PID映射到工作进程ID

  1. // Init data for worker id.
  2. self::initId();
  3. protected static function initId()
  4. {
  5. foreach (self::$_workers as $worker_id => $worker) {
  6. $new_id_map = array();
  7. for($key = 0; $key < $worker->count; $key++) {
  8. $new_id_map[$key] = isset(self::$_idMap[$worker_id] [$key]) ? self::$_idMap[$worker_id][$key] : 0;
  9. }
  10. self::$_idMap[$worker_id] = $new_id_map;
  11. }
  12. }

self::forkWorkers()方法通过循环self::$_workers数组,fork各自worker的count数量的进程。然后通过调用

$worker->run();

运行当前worker实例,在run方法中通过

  1. if (!self::$globalEvent) {
  2. $event_loop_class = self::getEventLoopName();
  3. self::$globalEvent = new $event_loop_class;
  4. // Register a listener to be notified when server socket is ready to read.
  5. if ($this->_socketName) {
  6. if ($this->transport !== ‘udp’) {
  7. self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ,
  8. array($this, ‘acceptConnection’));
  9. } else {
  10. self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ,
  11. array($this, ‘acceptUdpConnection’));
  12. }
  13. }

获取一个当前可用的事件轮询方式,然后根据当前的协议类型添加一个监听到事件轮询中
然后,尝试出发当前进程模型的onWorkerStart回调,此回调会在Gateway类以及BusinessWorker类中都会定义,代码

  1. if ($this->onWorkerStart) {
  2. try {
  3. call_user_func($this->onWorkerStart, $this);
  4. } catch (\Exception $e) {
  5. self::log($e);
  6. // Avoid rapid infinite loop exit.
  7. sleep(1);
  8. exit(250);
  9. } catch (\Error $e) {
  10. self::log($e);
  11. // Avoid rapid infinite loop exit.
  12. sleep(1);
  13. exit(250);
  14. }
  15. }

最后,执行事件的循环等待socket事件,处理读写等操作,代码

  1. // Main loop.
  2. self::$globalEvent->loop();

以上是runAll()方法的部分内容,会在了解GatewayWorker的工作原理的时候用到

2.1 Gateway进程向Register服务进程发起长连接注册自己

初始化Gateway

$gateway = new Gateway("text://0.0.0.0:8383");

在Gateway类中重写run方法,当调用runAll()方法启动进程时,fork进程之后,运行worker实例的时候,会调用到此重写的run方法

  1. public function run()
  2. {
  3. // 保存用户的回调,当对应的事件发生时触发
  4. $this->_onWorkerStart = $this->onWorkerStart;
  5. $this->onWorkerStart = array($this, ‘onWorkerStart’);
  6. // 保存用户的回调,当对应的事件发生时触发
  7. $this->_onConnect = $this->onConnect;
  8. $this->onConnect = array($this, ‘onClientConnect’);
  9. // onMessage禁止用户设置回调
  10. $this->onMessage = array($this, ‘onClientMessage’);
  11. // 保存用户的回调,当对应的事件发生时触发
  12. $this->_onClose = $this->onClose;
  13. $this->onClose = array($this, ‘onClientClose’);
  14. // 保存用户的回调,当对应的事件发生时触发
  15. $this->_onWorkerStop = $this->onWorkerStop;
  16. $this->onWorkerStop = array($this, ‘onWorkerStop’);
  17. $this->_startTime = time();
  18. // 运行父方法
  19. parent::run();
  20. }

定义了$this->onWorkerStart回调,

$this->onWorkerStart  = array($this, 'onWorkerStart');

 

 

执行到Worker类中的run()方法时,被触发。即,上边提到的

Worker脚本中的run方法

调用Gateway类中的onWorkerStart方法,代码

  1. public function onWorkerStart()
  2. {
  3. $this->lanPort = $this->startPort + $this->id;
  4. if ($this->pingInterval > 0) {
  5. $timer_interval = $this->pingNotResponseLimit > 0 ? $this->pingInterval / 2 : $this->pingInterval;
  6. Timer::add($timer_interval, array($this, ‘ping’));
  7. }
  8. if ($this->lanIp !== ‘127.0.0.1’) {
  9. Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, ‘pingBusinessWorker’));
  10. }
  11. if (strpos($this->registerAddress, ‘127.0.0.1’) !== 0) {
  12. Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, ‘pingRegister’));
  13. }
  14. if (!class_exists(‘\Protocols\GatewayProtocol’)) {
  15. class_alias(‘GatewayWorker\Protocols\GatewayProtocol’, ‘Protocols\GatewayProtocol’);
  16. }
  17. // 初始化 gateway 内部的监听,用于监听 worker 的连接已经连接上发来的数据
  18. $this->_innerTcpWorker = new Worker(“GatewayProtocol://{$this->lanIp}:{$this->lanPort});
  19. $this->_innerTcpWorker->listen();
  20. // 重新设置自动加载根目录
  21. Autoloader::setRootPath($this->_autoloadRootPath);
  22. // 设置内部监听的相关回调
  23. $this->_innerTcpWorker->onMessage = array($this, ‘onWorkerMessage’);
  24. $this->_innerTcpWorker->onConnect = array($this, ‘onWorkerConnect’);
  25. $this->_innerTcpWorker->onClose = array($this, ‘onWorkerClose’);
  26. // 注册 gateway 的内部通讯地址,worker 去连这个地址,以便 gateway 与 worker 之间建立起 TCP 长连接
  27. $this->registerAddress();
  28. if ($this->_onWorkerStart) {
  29. call_user_func($this->_onWorkerStart, $this);
  30. }
  31. }

$this->startPort : 内部通讯起始端口,假如$gateway->count=4,起始端口为4000,可在gateway启动脚本中自定义
$this->id : 基于worker实例分配的进程编号,当前从0开始,根据count自增。在fork进程的时候生成

Worker.php

$this->_innerTcpWorker:用于监听 worker 的连接已经连接上发来的数据。在工作原理5中,BusinessWorker进程得到所有的Gateway内部通讯地址后尝试连接Gateway以及其他两者之间的通信(连接,消息,关闭)会被调用
$this->registerAddress(): 代码中$this->registerAddress是在start_gateway.php初始化Gateway类之后定义的。该端口是Register进程所监听。此处异步的向Register进程发送数据,存储当前 Gateway 的内部通信地址

 

  1. public function registerAddress()
  2. {
  3. $address = $this->lanIp . ‘:’ . $this->lanPort;
  4. $this->_registerConnection = new AsyncTcpConnection(“text://{$this->registerAddress});
  5. $this->_registerConnection->send(‘{“event”:”gateway_connect”, “address”:”‘ . $address . ‘”, “secret_key”:”‘ . $this->secretKey . ‘”}’);
  6. $this->_registerConnection->onClose = array($this, ‘onRegisterConnectionClose’);
  7. $this->_registerConnection->connect();
  8. }

$this->lanIp: Gateway所在服务器的内网IP

2.2 BusinessWorker进程向Register服务进程发起长连接注册自己

BusinessWorker类中同样重写run方法,定义了$this->onWorkerStart

  1. public function run()
  2. {
  3. $this->_onWorkerStart = $this->onWorkerStart;
  4. $this->_onWorkerReload = $this->onWorkerReload;
  5. $this->_onWorkerStop = $this->onWorkerStop;
  6. $this->onWorkerStop = array($this, ‘onWorkerStop’);
  7. $this->onWorkerStart = array($this, ‘onWorkerStart’);
  8. $this->onWorkerReload = array($this, ‘onWorkerReload’);
  9. parent::run();
  10. }

执行Worker类中的run方法,触发BusinessWorker中的onWorkerStart

  1. protected function onWorkerStart()
  2. {
  3. if (!class_exists(‘\Protocols\GatewayProtocol’)) {
  4. class_alias(‘GatewayWorker\Protocols\GatewayProtocol’, ‘Protocols\GatewayProtocol’);
  5. }
  6. $this->connectToRegister();
  7. \GatewayWorker\Lib\Gateway::setBusinessWorker($this);
  8. \GatewayWorker\Lib\Gateway::$secretKey = $this->secretKey;
  9. if ($this->_onWorkerStart) {
  10. call_user_func($this->_onWorkerStart, $this);
  11. }
  12. if (is_callable($this->eventHandler . ‘::onWorkerStart’)) {
  13. call_user_func($this->eventHandler . ‘::onWorkerStart’, $this);
  14. }
  15. if (function_exists(‘pcntl_signal’)) {
  16. // 业务超时信号处理
  17. pcntl_signal(SIGALRM, array($this, ‘timeoutHandler’), false);
  18. } else {
  19. $this->processTimeout = 0;
  20. }
  21. // 设置回调
  22. if (is_callable($this->eventHandler . ‘::onConnect’)) {
  23. $this->_eventOnConnect = $this->eventHandler . ‘::onConnect’;
  24. }
  25. if (is_callable($this->eventHandler . ‘::onMessage’)) {
  26. $this->_eventOnMessage = $this->eventHandler . ‘::onMessage’;
  27. } else {
  28. echo “Waring: {$this->eventHandler}::onMessage is not callable\n”;
  29. }
  30. if (is_callable($this->eventHandler . ‘::onClose’)) {
  31. $this->_eventOnClose = $this->eventHandler . ‘::onClose’;
  32. }
  33. // 如果Register服务器不在本地服务器,则需要保持心跳
  34. if (strpos($this->registerAddress, ‘127.0.0.1’) !== 0) {
  35. Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, ‘pingRegister’));
  36. }
  37. }

通过connectToRegister方法,发送数据到Register进程,连接服务注册中心

  1. public function connectToRegister()
  2. {
  3. $this->_registerConnection = new AsyncTcpConnection(“text://{$this->registerAddress});
  4. $this->_registerConnection->send(‘{“event”:”worker_connect”,”secret_key”:”‘ . $this->secretKey . ‘”}’);
  5. $this->_registerConnection->onClose = array($this, ‘onRegisterConnectionClose’);
  6. $this->_registerConnection->onMessage = array($this, ‘onRegisterConnectionMessage’);
  7. $this->_registerConnection->connect();
  8. }

3 Register服务收到Gateway的注册后,把所有的Gateway的通讯地址保存在内存中

在Register类中,重写了run方法,定义了当前的

  1. $this->onConnect = array($this, ‘onConnect’);
  2. // 设置 onMessage 回调
  3. $this->onMessage = array($this, ‘onMessage’);
  4. // 设置 onClose 回调
  5. $this->onClose = array($this, ‘onClose’);

三个属性,当Register启动的进程收到消息时,会触发onMessage方法

  1. public function onMessage($connection, $buffer)
  2. {
  3. // 删除定时器
  4. Timer::del($connection->timeout_timerid);
  5. $data = @json_decode($buffer, true);
  6. if (empty($data[‘event’])) {
  7. $error = “Bad request for Register service. Request info(IP:”.$connection->getRemoteIp().“, Request Buffer:$buffer). See http://wiki.workerman.net/Error4 for detail”;
  8. Worker::log($error);
  9. return $connection->close($error);
  10. }
  11. $event = $data[‘event’];
  12. $secret_key = isset($data[‘secret_key’]) ? $data[‘secret_key’] : ;
  13. // 开始验证
  14. switch ($event) {
  15. // 是 gateway 连接
  16. case ‘gateway_connect’:
  17. if (empty($data[‘address’])) {
  18. echo “address not found\n”;
  19. return $connection->close();
  20. }
  21. if ($secret_key !== $this->secretKey) {
  22. Worker::log(“Register: Key does not match “.var_export($secret_key, true).” !== “.var_export($this->secretKey, true));
  23. return $connection->close();
  24. }
  25. $this->_gatewayConnections[$connection->id] = $data[‘address’];
  26. $this->broadcastAddresses();
  27. break;
  28. // 是 worker 连接
  29. case ‘worker_connect’:
  30. if ($secret_key !== $this->secretKey) {
  31. Worker::log(“Register: Key does not match “.var_export($secret_key, true).” !== “.var_export($this->secretKey, true));
  32. return $connection->close();
  33. }
  34. $this->_workerConnections[$connection->id] = $connection;
  35. $this->broadcastAddresses($connection);
  36. break;
  37. case ‘ping’:
  38. break;
  39. default:
  40. Worker::log(“Register unknown event:$event IP: “.$connection->getRemoteIp().” Buffer:$buffer. See http://wiki.workerman.net/Error4 for detail”);
  41. $connection->close();
  42. }
  43. }

当$event = ‘gateway_connect’时,是Gateway发来的注册消息,保存到$this->_gatewayConnections数组中,在通过broadcastAddresses方法将当前$this->_gatewayConnections中所有的Gatewat通讯地址转发给所有BusinessWorker进程

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

 

 

同第3步中,Register类收到BusinessWorker的注册时,会触发onMessage方法中的worker_connect,case选项。

image.png

 

同时,将当前worker连接加入到$_workerConnections数组中,在通过broadcastAddresses方法将当前$this->_gatewayConnections中所有的Gatewat通讯地址转发给所有BusinessWorker进程。

5 BusinessWorker进程得到所有的Gateway内部通讯地址后尝试连接Gateway

在BusinessWoker类的启动中,通过重写run方法,定义的启动onWorkerStart方法中,通过connectToRegister方法注册服务中心的同时,也定义了onMessage匿名函数,用于接收消息回调。

$this->_registerConnection->onMessage = array($this, 'onRegisterConnectionMessage');

即,当注册中心发来消息时候,回调到此处

  1. public function onRegisterConnectionMessage($register_connection, $data)
  2. {
  3. $data = json_decode($data, true);
  4. if (!isset($data[‘event’])) {
  5. echo “Received bad data from Register\n”;
  6. return;
  7. }
  8. $event = $data[‘event’];
  9. switch ($event) {
  10. case ‘broadcast_addresses’:
  11. if (!is_array($data[‘addresses’])) {
  12. echo “Received bad data from Register. Addresses empty\n”;
  13. return;
  14. }
  15. $addresses = $data[‘addresses’];
  16. $this->_gatewayAddresses = array();
  17. foreach ($addresses as $addr) {
  18. $this->_gatewayAddresses[$addr] = $addr;
  19. }
  20. $this->checkGatewayConnections($addresses);
  21. break;
  22. default:
  23. echo “Receive bad event:$event from Register.\n”;
  24. }
  25. }

其中Register类发来的数据是

  1. $data = array(
  2. ‘event’ => ‘broadcast_addresses’,
  3. ‘addresses’ => array_unique(array_values($this->_gatewayConnections)),
  4. );

这个时候,就会通过checkGatewayConnections方法检查gateway的这些通信端口是否都已经连接,在通过tryToConnectGateway方法尝试连接gateway的这些内部通信地址

6 Gateway进程收到BusinessWorker进程的连接消息

同样,在Gateway进程启动的时候,触发的onWorkerStart方法中,也定义了一个内部通讯的onWorkerMessage

$this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage');

由此来接收BusinessWorker进程发来的连接消息,部分代码

  1. public function onWorkerMessage($connection, $data)
  2. {
  3. $cmd = $data[‘cmd’];
  4. if (empty($connection->authorized) && $cmd !== GatewayProtocol::CMD_WORKER_CONNECT && $cmd !== GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT) {
  5. self::log(“Unauthorized request from “ . $connection->getRemoteIp() . “:” . $connection->getRemotePort());
  6. return $connection->close();
  7. }
  8. switch ($cmd) {
  9. // BusinessWorker连接Gateway
  10. case GatewayProtocol::CMD_WORKER_CONNECT:
  11. $worker_info = json_decode($data[‘body’], true);
  12. if ($worker_info[‘secret_key’] !== $this->secretKey) {
  13. self::log(“Gateway: Worker key does not match “.var_export($this->secretKey, true).” !== “. var_export($this->secretKey));
  14. return $connection->close();
  15. }
  16. $key = $connection->getRemoteIp() . ‘:’ . $worker_info[‘worker_key’];
  17. // 在一台服务器上businessWorker->name不能相同
  18. if (isset($this->_workerConnections[$key])) {
  19. self::log(“Gateway: Worker->name conflict. Key:{$key});
  20. $connection->close();
  21. return;
  22. }
  23. $connection->key = $key;
  24. $this->_workerConnections[$key] = $connection;
  25. $connection->authorized = true;
  26. return;
  27. // GatewayClient连接Gateway

将worker的进程连接保存到$this->_workerConnections[$key] = $connection;

7 Gateway进程收到客户端的连接,消息时,会通过Gateway转发给worker处理

  1. // Gateway类的run方法中定义此属性
  2. $this->onMessage = array($this, ‘onClientMessage’);
  3. // 收到客户端消息的时候出发此函数
  4. public function onClientMessage($connection, $data)
  5. {
  6. $connection->pingNotResponseCount = –1;
  7. $this->sendToWorker(GatewayProtocol::CMD_ON_MESSAGE, $connection, $data);
  8. }

在sendToWorker方法中,将数据发给worker进程处理

PR教程: PR一键去水印教程, 这样去水印即快又干净! - 知乎

mikel阅读(1201)

来源: PR教程: PR一键去水印教程, 这样去水印即快又干净! – 知乎

在后期制作,视频剪辑工作中,去水印是经常遇到的活(ps:虽然这活在工作中应用的比较频繁,不建议大家常用,尊重人家的知识产权,即使使用,也要取得原作者的同意哦),作为从事影视后期制作的我们,掌握有效去水印的方法,是吃这口饭的必备技能哦!

去水印的方法有很多,使用比较多的是“裁切画面”“模糊马赛克等”,但这些最终的效果都不尽如人意哦!今天大叔带来这个特效去水印的方法,效果 nice!

本期大叔就把AE实景运动文字合成教程分享给小伙伴,干货哦~PS:主要解析PR特效插件:视频去水印的使用方法!

PR去水印文图解析

1、(如下图)特效—中间值—拖入视频镜头素材

2、(如下图)点选矩形蒙版—调整蒙版形状,使其覆盖到水印所在的区域!

3、(如下图)调整参数半价和蒙版羽化各位35即可!

视频网站获取M3U8链接,来下载完整视频_Crayonxin2000的博客-CSDN博客_m3u8链接

mikel阅读(2246)

来源: 视频网站获取M3U8链接,来下载完整视频_Crayonxin2000的博客-CSDN博客_m3u8链接

前言
网络上的视频大多都经过切片处理,用idm下载视频,有些能下载完整的视频,但是很多都是一些视频小片段,如下图展示的鹅厂的ts片段(爱奇艺是.f4v)

 

后期可以通过ffmpeg或者一些其他程序来合并,但是有点麻烦。而且idm也不是万能的,有些网站idm是不能下载视频的。
这里我来介绍一种通过获取m3u8来下载完整视频(无需idm),以鹅厂视频为例。

获取m3u8链接
m3u8链接长这样:
https://apd-ef3f9709dae705ed2f13a9e885e1a1bf.v.smtcdns.com/varietyts.tc.qq.com/Axf21aHSim4PktKAuXtWvQsBkEOptU6pommPaiNFWIMg/uwMROfz2r5zCIaQXGdGnC2df644Q3LWUuLvyGY4RMhgE_3T2/yL3J5gp1e6f93V9mlCZVgk26P50wKzEcNG9T4e3lRTKvJ5GER05sFlYdcDuVg2_Scuo4C7t3zFBFnUyyxoAFlTMO-wnLMytwjxnfUSQ4Bu8mNCW5aq8JlI1orZ5YgStoc9X3sMAdVnTJd3d7o9RT0UDVWwpENQgU-3eCKCMXFLo/h0035uysemv.321002.ts.m3u8?ver=4

有没有发现链接带有“m3u8”字样?咱们就是要找这样的链接

补充:
其实可以借助浏览器插件或者油猴脚本来直接获取m3u8链接。我觉得获取链接直接用谷歌浏览器自带的开发者工具就可以完成。而且视频网站的代码更新的很快,使用第三方的工具可能出现不能用的情况。下面介绍使用谷歌浏览器自带的开发者工具来获取。如果您已经知道怎么获取m3u8链接了,可以跳过本节,直接去看下一标题内容

打开你想要看的视频,再按f12打开开发者工具。再刷新一下。播放视频再立即关闭。在开发者工具中选中Network,再在过滤器filter中输入 “ m3u8 ” ,会出现几条http请求。

普通的视频网站的m3u8链接应该会直接出现,直接copy 链接就行了。如下图

 

但是鹅厂比较鬼,他把m3u8链接藏在了参数里,需要点开请求,找到vurl参数

复制这个参数就可以得m3u8链接了。
获取到链接接下来就容易了

使用m3u8工具来下载文件
m3u8工具有很多。这里我使用的是网上一位大神写的下载工具,虽然比较老了,还是挺好用的。你们也可以使用其他的软件。

这里是下载链接:M3U8 Downloader

把链接粘贴到地址栏。
文件格式,下载路径选择好,就可以开始下载了。

还有一款批量下载m3u8链接的软件:
m3u8批量
这个软件主要是使用的是aria2和ffmpeg配合下载拼装

总结
其实寻找m3u8链接是最难的。不同家的视频网站,链接的放的位置也不一样,根据实际情况灵活运用

补充
这个方法只适合哪些使用m3u8形式来获取视频流目录的视频网站。本人亲测鹅厂的是完全没问题,还有大多数普通视频网站都是可以的
爱奇艺不可以。
可以关注我,后期发一篇关于爱奇艺下载的
————————————————
版权声明:本文为CSDN博主「Crayon鑫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Crayonxin2000/article/details/113796821

 GatewayWorker客户端向服务端发送心跳请求及心跳检测的实现_sinat_34469308的博客-CSDN博客

mikel阅读(1803)

来源: (1条消息) GatewayWorker客户端向服务端发送心跳请求及心跳检测的实现_sinat_34469308的博客-CSDN博客

现在在做一个功能,就是服务端要知道客户端何时掉线的功能,那么要实现这个功能,首先需要实现客户端向服务端发送心跳请求,以及服务端为客户端进行心跳检测的功能。

为了实现这个功能,我选择使用GatewayWorker框架,刚刚接触这个框架,实现了一个简单的客户端心跳检测。

首先,在服务端设置心跳检测:

服务端的心跳检测的设置,需要在start_gateway.php文件中进行设置,start_gateway.php文件是 gateway进程启动脚本,包括端口号等设置。

// 心跳间隔
$gateway->pingInterval = 15;
$gateway->pingNotResponseLimit = 1;
// 心跳数据
$gateway->pingData = ”;
1
2
3
4
5
代码含义:
(1)$gateway->pingInterval = 15; 心跳检测的时间间隔为15秒

(2)$gateway->pingNotResponseLimit = 1; 心跳检测的时间间隔数

(3)$gateway->pingData = ‘’;服务端定时向客户端发送的数据(暂不考虑)

(4)客户端定时向服务端发送心跳,那么g a t e w a y − &gt; p i n g N o t R e s p o n s e L i m i t 必 须 要 大 于 0 , 如 果 gateway-&gt;pingNotResponseLimit 必须要大于0,如果gateway−>pingNotResponseLimit必须要大于0,如果gateway->pingNotResponseLimit =0,就表示客户端不向服务端发送心跳,服务端即使没有收到客户端的心跳,也不会断开连接,更不会触发onClose回调函数。

(5)$gateway->pingInterval x $gateway->pingNotResponseLimit 的值,就是心跳时间期限。

(6)上面代码的含义就是在 15(15×1) 秒的时间里,如果服务端没有检测到客户端发送的心跳请求,那么服务端就认为客户端已经掉线了,服务端自动触发onClose回调函数,进行客户端掉线的善后工作。

需要说明的是,在start_gateway.php文件中进行设置的心跳检测,只是相当于一个config的设置操作,并没有实现任何客户端心跳数据的发送,以及服务端心跳数据的接收和心跳停止后所触发的后续功能。只是起到一个心跳检测的设置作用。

在客户端设置心跳请求:

在客户端设置心跳请求,就是在前端页面(浏览器)里编写js代码,通过在html页面中的js代码,实现前端(客户端)发送心跳请求,

先实现了一个简单的心跳请求测试:

<script>
var ws = new WebSocket(“ws://127.0.0.1:8282”);
ws.onopen = function(){
console.info(“与服务端连接成功”);
ws.send(‘test msg\n’);//相当于发送一个初始化信息
console.info(“向服务端发送心跳包字符串”);
setInterval(show,3000);
}

function show(){
ws.send(‘heart beat\n’);
}

ws.onConnect = function(e){

}
ws.onmessage = function(e){
console.log(e.data);
}
//心跳处理
//获取会员id
ws.onclose = function(e){
console.log(e);
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这个代码很简单,代码解释:
(1) var ws = new WebSocket(“ws://127.0.0.1:8282”); 实例化gatewayworker,
(2)通过onopen属性,与服务端建立连接,

ws.onopen = function(){
console.info(“与服务端连接成功”);
ws.send(‘test msg\n’);//相当于发送一个初始化信息
console.info(“向服务端发送心跳包字符串”);
setInterval(show,3000);
}
1
2
3
4
5
6
这里要说的是,客户端与服务端建立连接,我没有在gatewayworker和workerman里找到相应的方法,所以只能用websocket的onopen属性来实现。
(3)在客户端的定时器,采用了setInterval(show,3000);来实现,每3秒执行一次show()函数。
(4)通过show()函数来向服务端发送心跳数据。

function show(){
ws.send(‘heart beat\n’);
}
1
2
3
服务端心跳请求的处理:
服务端处理心跳,都是在gatewayworker的业务文件——events.php进行的,
这里只写一下onclose函数,就是当gatewayworker检测到客户端心跳停止(比如断电,关闭页面,网线被女朋友拔掉,死机等),就自动触发onclose函数,那么客户端心跳停止后的善后功能,都可以在onclose函数中加以实现,在这个测试案例中,onclose函数实现了发送心跳停止的提示语句。

/**
* 当用户断开连接时触发
* @param int $client_id 连接id
*/
public static function onClose($client_id)
{
// 向所有人发送
echo “the heart beats stoped.\n”;
echo “the user logouted.\n”;

}
1
2
3
4
5
6
7
8
9
10
11
运行结果:

打开浏览器,打开客户端页面,客户端通过setInterval(show,3000)函数,自动发送心跳请求(向服务端发送heart beat字样的信息),因为客户端是每3秒发送一次心跳,符合服务端心跳检测的15秒内需有心跳的心跳检测规则,所以一切正常,当客户端页面关闭,心跳停止,服务端发现15秒内没有一次心跳,就认为客户端已经下线,自动触发服务端的onclose函数,

以上就是使用gatewayworker,进行客户端向服务端发送心跳请求,以及服务端进行客户端的心跳检测的简单实现,

这里有几点想要说的是:
(1)gatewayworker为客户端提供的接口或者方法并不多,比如客户端的定时器,客户端向服务端发送信息,以及客户端与服务端进行连接这些功能的实现,都需要使用websocket的属性和方法实现,gatewayworker并没有提供客户端方法的支持。
(2)正因为(1),对于不支持websocket的浏览器,就会产生功能性的问题,所以在后面还需要使用socket.io等库进行浏览器兼容性的处理。
(3)关于gatewayworker的定时器:

Timer::add(10, function(){
echo “timer\n”;
});
1
2
3
gatewayworker或者workerman的定时器,都是针对服务端的,这个Timer定时器类,是服务器端定时实现某些功能操作,而跟客户端的定时器无关,所以客户端要实现心跳功能,用不上Timer定时器类啊,还是需要客户端自行编写定时器功能。
————————————————
版权声明:本文为CSDN博主「sinat_34469308」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_34469308/article/details/83757183

格式化时间戳,时间加一天_张广森的博客-CSDN博客_时间戳加一天

mikel阅读(679)

来源: (1条消息) 格式化时间戳,时间加一天_张广森的博客-CSDN博客_时间戳加一天

$ceshi=”1417247764″; //时间戳格式

第一种:

$ceshi+86400;//在时间戳的基础上加一天(即60*60*24)
第二种:

$firstdaystr=date(“Y-m-d H:i:s”,$ceshi); //格式化时间戳,转为正常格式 2014-12-18
//$end_time=strtotime($firstdaystr.” +24 hours”); //把时间加24小时,就是加一天。并且是时间戳的格式

$end_time=date(“Y-m-d H:i”,strtotime($firstdaystr.” +24 hours”)); //时间加一天,时间转为正常格式

一些例子:

date(‘Y-m-d H:i:s’,strtotime(‘+1 day’));
date(‘Y-m-d H:i:s’,strtotime(“+1 day +1 hour +1 minute”);
可以修改参数1为任何想需要的数 day也可以改成year(年),month(月),hour(小时),minute(分),second(秒)
$tomorrow = date(“Y-m-d”,mktime (0,0,0,date(“m”) ,date(“d”)+1,date(“Y”)));
<?php
echo(strtotime(“now”));
echo(strtotime(“3 October 2005”));
echo(strtotime(“+5 hours”));
echo(strtotime(“+1 week”));
echo(strtotime(“+1 week 3 days 7 hours 5 seconds”));
echo(strtotime(“next Monday”));
echo(strtotime(“last Sunday”));
?>
一,PHP时间戳函数获取指定日期的unix时间戳 strtotime(”2009-1-22″) 示例如下:

echo strtotime(”2009-1-22″) 结果:1232553600

说明:返回2009年1月22日0点0分0秒时间戳

二,PHP时间戳函数获取英文文本日期时间 示例如下:

便于比较,使用date将当时间戳与指定时间戳转换成系统时间

(1)打印明天此时的时间戳strtotime(”+1 day”)

当前时间:echo date(”Y-m-d H:i:s”,time()) 结果:2009-01-22 09:40:25

指定时间:echo date(”Y-m-d H:i:s”,strtotime(”+1 day”)) 结果:2009-01-23 09:40:25

(2)打印昨天此时的时间戳strtotime(”-1 day”)

当前时间:echo date(”Y-m-d H:i:s”,time()) 结果:2009-01-22 09:40:25

指定时间:echo date(”Y-m-d H:i:s”,strtotime(”-1 day”)) 结果:2009-01-21 09:40:25

(3)打印下个星期此时的时间戳strtotime(”+1 week”)

当前时间:echo date(”Y-m-d H:i:s”,time()) 结果:2009-01-22 09:40:25

指定时间:echo date(”Y-m-d H:i:s”,strtotime(”+1 week”)) 结果:2009-01-29 09:40:25

(4)打印上个星期此时的时间戳strtotime(”-1 week”)

当前时间:echo date(”Y-m-d H:i:s”,time()) 结果:2009-01-22 09:40:25

指定时间:echo date(”Y-m-d H:i:s”,strtotime(”-1 week”)) 结果:2009-01-15 09:40:25

(5)打印指定下星期几的时间戳strtotime(”next Thursday”)

当前时间:echo date(”Y-m-d H:i:s”,time()) 结果:2009-01-22 09:40:25

指定时间:echo date(”Y-m-d H:i:s”,strtotime(”next Thursday”)) 结果:2009-01-29 00:00:00

(6)打印指定上星期几的时间戳strtotime(”last Thursday”)

当前时间:echo date(”Y-m-d H:i:s”,time()) 结果:2009-01-22 09:40:25

指定时间:echo date(”Y-m-d H:i:s”,strtotime(”last Thursday”)) 结果:2009-01-15 00:00:00

以上PHP时间戳函数示例可知,strtotime能将任何英文文本的日期时间描述解析为Unix时间戳,我们结合mktime()或date()格式化日期时间获取指定的时间戳,实现所需要的日期时间。

PHP实现mysql事务处理_LSGOZJ的博客-CSDN博客

mikel阅读(571)

来源: PHP实现mysql事务处理_LSGOZJ的博客-CSDN博客

想要实现事务管理,操作的表引擎类型必须是 InnoDB 类型,在生成表的时候就要声明,因为 mySQL 默认引擎是 MYISAM ;当然,你也可以在生成表之后修改表的引擎:

ALTER TABLE orders ENGINE=INNODB;
1
更多详细可以参照我的另一篇博客:《关于mySQL管理事务处理》,这里用到的表都是那篇博客中创建的表。

下面先写一个小栗子(往orders表和orderdetail表存数据):

<?php
//数据库连接
$link = mysql_connect(“localhost”,’root’,’root’) or die(“数据库链接失败!”);
//选择数据库
// $db = mysql_select_db(“test_mysql”,$link);
$db = mysql_query(“USE test”);
if(!$db){
echo “数据库连接失败!”;
}
mysql_query(“SET NAMES UTF8”);

//开启事务
$sql1 = “START TRANSACTION”;
mysql_query($sql1);

$ins_sql1 = “INSERT INTO orders(user_id,order_no) VALUES(10,’LSGO123′)”;
$err1 = mysql_query($ins_sql1);

$ins_sql2 = “INSERT INTO orderdetail(order_no,detail) VALUES(‘LSGO123′,’LSGO实验室’)”;
$err2 = mysql_query($ins_sql2);

if(!$err1 || !$err2){
echo “存表出错了”;
mysql_query(“ROLLBACK”);
}else{
echo “存表正确”;
mysql_query(“COMMIT”);
}

mysql_close();
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
结果输出“存表正确。”,检查数据库:

mysql> SELECT * FROM orders;
+—-+———+———-+
| id | user_id | order_no |
+—-+———+———-+
| 30 | 10 | LSGO123 |
+—-+———+———-+
1 row in set

mysql> SELECT * FROM orderdetail;
+—-+———-+———-+
| id | order_no | detail |
+—-+———-+———-+
| 12 | LSGO123 | LSGO实验室|
+—-+———-+———-+
1 row in set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这证明我们存表确实成功。

现在我们卡一下,使其中的一个表存失败:

$ins_sql1 = “INSERT INTO orders(user_id,order_no) VALUES(10,’LSGO123′)”;
$err1 = mysql_query($ins_sql1);

$ins_sql2 = “INSERT INTO orderdetail(order_no,detail) VALUES(‘LSGO123’,LSGO实验室)”;//这里我故意使出错。
$err2 = mysql_query($ins_sql2);

if(!$err1 || !$err2){
echo “存表出错了”;
mysql_query(“ROLLBACK”);
}else{
echo “存表正确”;
mysql_query(“COMMIT”);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
结果返回“存表失败了”,检查两张表,发现都没存进去,证明事务起作用了。
通过以上的例子,我们可以看到,事务处理可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行,也就是说当要求某几个表同时存或同时不存的时候,就该用事务了。

现在该说一下事务里面的保留点了

引用前面说的那篇博客里面一个例子:
现在假想这么一个问题,当一个新用户购买我的东西的时候,我要分别存customer表、orders表、orderdetail表,假如某个故障阻止了这个存储过程,那么数据库会发生什么?
1、customer表存失败了,那么我们就不能让它继续往orders和orderdetail表存。
2、customer成功了,但orders表失败了,那么customer是可以允许成功的,毕竟某个用户没有订单是完全合法的,但是orders失败了,orderdetail就不能往下存了,因为orders和orderdetail是同步的。

怎么实现?这里用保留点实现部分回退事务。

//开启事务
$sql1 = “START TRANSACTION”;
mysql_query($sql1);

$ins_sql3 = “INSERT INTO customer(name,age) VALUES(‘奔跑吧LSGO’,20)”;
$err3 = mysql_query($ins_sql3);

//设置保留点
mysql_query(“SAVEPOINT ins_1_2”);

$ins_sql1 = “INSERT INTO orders(user_id,order_no) VALUES(10,’LSGO123′)”;
$err1 = mysql_query($ins_sql1);

$ins_sql2 = “INSERT INTO orderdetail(order_no,detail) VALUES(‘LSGO123′,’LSGO实验室’)”;
$err2 = mysql_query($ins_sql2);

if(!$err1 || !$err2){
//回滚至保留点处,即不存orders表和orderdetail表
echo “orders和orderdetail存失败了!”;
mysql_query(“ROLLBACK TO SAVEPOINT ins_1_2”);
}
if($err3){
echo “存表成功!”;
mysql_query(“COMMIT”);
}else{
echo “customer存失败了!”;
mysql_query(“ROLLBACK”);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
结果返回“存表成功”,就是整个事务都成功,三个表自然也存了;

再来?

//开启事务
$sql1 = “START TRANSACTION”;
mysql_query($sql1);

$ins_sql3 = “INSERT INTO customer(name,age) VALUES(奔跑吧LSGO,20)”;//注意,这里我故意让他出错
$err3 = mysql_query($ins_sql3);

//设置保留点
mysql_query(“SAVEPOINT ins_1_2”);

$ins_sql1 = “INSERT INTO orders(user_id,order_no) VALUES(10,’LSGO123′)”;
$err1 = mysql_query($ins_sql1);

$ins_sql2 = “INSERT INTO orderdetail(order_no,detail) VALUES(‘LSGO123′,’LSGO实验室’)”;
$err2 = mysql_query($ins_sql2);

if(!$err1 || !$err2){
//回滚至保留点处,即不存orders表和orderdetail表
echo “orders或orderdetail存失败了!”;
mysql_query(“ROLLBACK TO SAVEPOINT ins_1_2”);
}
if($err3){
echo “存表成功!”;
mysql_query(“COMMIT”);
}else{
echo “customer存失败了!”;
mysql_query(“ROLLBACK”);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
结果返回“customer存失败了!”,按照问题的要求,customer表失败,其它两个表也失败了,事实上也是这样的,orders表和orderdetail表没有存。

当我使保留点后的代码出错,结果返回“orders或orderdetail存失败了!”,然后看数据库,发现customer表存成功了,其它两表存失败了。这也满足了问题的要求。

在网上看到一篇博客说在表引擎不是INNODB的情况下,我们也可以实现简单的事务处理,这里就叫它 “仿事务”吧,原理就是把mysql自动提交更改关掉,手动提交代码,就类似于手动COMMIT。
直接上代码:

mysql_query(“SET AUTOCOMMIT=0”); //设置mysql不自动提交,需自行用commit语句提交
$sql1 = “INSERT INTO customer(name,age) VALUES(‘LSGO实验室’,20)”;
$sql2 = “INSERT INTO orders(user_id,order_no) VALUES(12,’LSGO123′)”;
$res1 = mysql_query($sql1);
$res2 = mysql_query($sql2);
if($res1 && $res2){
mysql_query(“COMMIT”);
echo ‘提交成功。’;
}else{
mysql_query(“ROLLBACK”);
echo ‘数据回滚。’;
}
mysql_query(“SET AUTOCOMMIT=1”);
//事务处理完时别忘记mysql_query(“SET AUTOCOMMIT=1”);恢复自动提交,不然会影响到后面的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个方法固然是走得通的,但是没法实现部分回退的功能,因此还是推荐用前面介绍的方法。

事务处理在thinkphp 中相当简单,毕竟thinkphp中已经封装好相关方法,你只需要调用即可。

//实例化的数据库对象
$model = new Model();
//开启事务
$model->startTrans();
//这里你也可以用具体的一张表的实例即可,如
//$orders = M(‘Orders’);
//$orders->startTrans();

//数据操作
$orders = M(‘Orders’);
$customer = M(‘Customer’);
$res1 = $orders->where(“user_id = 10”)->delete();
$res2 = $customer->where(“age = 20”)->delete();

if($res1 && $res2){
echo “成功啦!”;
$model->commit();
}else{
echo “滚~~~”;
$model->rollback();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
至此,我掌握的事务处理已经讲完了。
————————————————
版权声明:本文为CSDN博主「LSGOZJ」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/baidu_30000217/article/details/50375074