swoole学习之: 服务端(异步风格)-TCP/UDP服务器 - Swoole\Server的方法(三)

本文摘自 https://wiki.swoole.com/#/server/methods , 有删减, 修改, 补充

task()

投递一个异步任务到 task_worker 池中。 task() 是非阻塞的, 执行完毕会立即返回。Worker进程可以继续处理新的请求。使用 Task 功能,必须先设置 task_worker_num ,且必须设置 Server onTask onFinish 事件回调函数。

Swoole\Server->task(mixed $data, int $dstWorkerId=-1, callable $finishCallback): int
  • int $dstWorkerId
    • 指定要投递给哪个Task进程, 传入Task进程的ID即可, 范围为 [0, $server->setting['task_worker_num'] - 1]
    • 默认值 -1 表示随机投递, 底层会自动选择一个空闲的Task进程
  • 返回值
    • 调用成功, 返回值为整数的 task_id . 如果有 finish 回调, 则在 onFinish 中携带参数 $task_id .
    • 调用失败, 返回值为 (bool)false , $task_id 可能为0
  • 提示
    • 此功能用于将慢速的任务异步地去执行, 比如一个聊天室服务器, 可以用它来发送广播. 当任务完成时, 在task进程中调换用 $server->finish('finish') 告诉 worker 进程此任务已完成(也可以选 Swoole\Server->finish ).
$server->set([
    'worker_num' => 4,
    'task_worker_num' => 64, //启动64个进程来接收异步任务
]);
$server->task($data, -1, function(Swoole\Server $server, $task_id, $data){
    echo 'Task callback:  task_id='.$task_id.PHP_EOL;
    var_dump($data);
});
  • $task_id 是从0-42亿的整数(unsigned int(4)????), 且在当前进程内是唯一的.
  • 默认不启动task功能, 需要通过 $server->set 设置 task_worker_num 来启动该功能.

单向任务 - 从 Master Manager UserProcess 进程中投递的任务是 单向 的。在 TaskWorker 进程中无法使用 return Server->finish() 方法返回结果数据。

注意:

  • task 方法不能在 task进程 / 用户自定义进程 中调用
  • 使用 task 必须为 Server 设置 onTask onFinish 回调事件, 否则 $server->start() 会失败
  • task操作的次数必须小于 onTask 处理速度. 如果投递容量超过处理能力, task 数据会塞满缓存区,导致 Worker 进程发生阻塞, Worker 将无法接受新的请求
  • 在使用 addProcess 添加的用户进程中无法使用task来投递任务, 请使用 sendMessage 接口与 Worker/Task 进程通信

示例:

$server = new Swoole\Server('127.0.0.1', 9501, SWOOLE_BASE);
$server->set([
    'worker_num' => 2,
    'task_worker_num' => 4,//必须设置, 以启动task模块
]);
//注意: `BASE`模式下没有`master`进程,因此不存在`onStart`事件
//$server->on('start', function($server){
//    echo 'Swoole Server started at '.date('Y-m-d H:i:s').PHP_EOL;
//});
$server->on('receive', function(Swoole\Server $server, $fd, $reactor_id, $data){
    $data = trim($data);
    echo '接收数据: '.$data.PHP_EOL;

    //启动task任务1: 随机投递到空闲的Task进程
    $server->task($data.'== 随机id', -1, function(Swoole\Server $server, $task_id, $data){
        //随机分配的话, 回调函数中会收到参数 task_id
        echo 'Task RANDOM callback: task_id='.$task_id.', time: '.date('Y-m-d H:i:s').', msg: '.$data.PHP_EOL;
    });

    //启动task任务2: 指定要投递的Task进程的id
    $task_id = $server->task($data.' == 指定id', 0, function(Swoole\Server $server, $task_id, $data){
        echo 'Task ASSUMED ID callback: task_id='.$task_id.', time: '.date('Y-m-d H:i:s').', msg: '.$data.PHP_EOL;
    });
    //给客户端发消息
    $server->send($fd, '分发任务, task_id='.$task_id.PHP_EOL);
});
//必须设置onTask和onFinish
//task收到任务
$server->on('task', function(Swoole\Server $server, $task_id, $reactor_id, $data){
    echo 'Task进程收到数据:'.date('Y-m-d H:i:s').PHP_EOL;
    echo '#'.$server->worker_id.', onTask: [pid='.$server->worker_pid.']: task_id='.$task_id.', data length: '. strlen($data).PHP_EOL;
    $server->finish($data);//执行完毕后通知Worker线程, 执行回调函数
});
//task异步任务完成
$server->on('finish', function(Swoole\Server $server, $task_id, $data){
    echo 'Task#'.$task_id.' finished, data length: '.strlen($data).PHP_EOL;
});

$server->on('workerStart', function($server, $worker_id){
    global $args;//可能为null
    echo 'workerStart: worker_id='.$worker_id.PHP_EOL;
    //如果当前worker进程id大于等于设置的进程数量, 则设置进程名称为:
    $arg0 = isset($args[0]) ? $args[0] : '';
    if($worker_id >= $server->setting['worker_num']){
       swoole_set_process_name('php '.$arg0.': task_worker');
    }else{
       swoole_set_process_name('php '.$arg0.': _worker');
    }
});
$server->start();
//start => onWorkerStart => onReceive => task() => onTask => onFinish
//这里的task有2次: 第一次是随机id, 第二次是指定id

taskwait()

taskwait() task() 作用相同, 用于投递一个异步的任务到 task 进程池去执行. 与task不同的是 taskwait 同步等待 的, 直到任务完成或者超时返回. $result 为任务执行的结果, 由 $server->finish() 函数发出. 如果任务超时, 则返回 ``(bool)false`.

Swoole\Server->taskwait(mixed $data, float $timeout=0.5, int $dstWorkerId=-1): string|bool
  • 提示:
    • 协程模式
      • 从4.0.4版本开始, taskwait 方法将支持 协程调度 , 在协程中调用Server->taskwait()时将自动进行协程调度,不再阻塞等待.
      • 借助协程调度器, taskwait 可以实现并发调用.
    • 同步模式
      • 在同步阻塞模式下, taskwait 需要使用 UnixSocket 通信和内存共享,将数据返回给Worker进程, 这个过程是同步阻塞的.
    • 特例 -如果 onTask 中没有任何 同步IO 操作, 底层仅有 2 次进程切换的开销, 并不会产生 IO 等待, 因此这种情况下 taskwait 可以视为 非阻塞 . 实际测试 onTask 中仅读写PHP数组 进行 10 万次 taskwait 操作, 总耗时仅为 1 秒, 平均每次耗时为 10 微秒.
  • 注意:
    • Swoole\Server->finish 不要使用 taskwait
    • taskwait 方法不能在 task 进程中调用

taskWaitMulti()

并发执行多个task异步任务, 该方法 不支持协程调度 ,会导致其他协程开始, 协程环境下需要用 taskCo 替换.

Swoole\Server->taskWaitMulti(array $tasks, float $timeout=0.5): array | bool
  • 参数
    • array $tasks 必须为数字索引数组, 不支持关联索引数组. 底层会遍历 $tasks 将任务逐个投递到 Task 进程.
  • 返回值:
    • 任务完成或超时, 返回结果数组. 结果数组中每个任务结果的顺序与 $tasks 对应. 比如: $tasks[2] 对应结果为 $result[2] .
    • 某个任务执行超时不会影响其他任务, 返回的结果数据中奖不包含超时的任务.
  • 注意: 最大并发任务不得超过1024
  • 示例:
$tasks[] = mt_rand(1000,999);//任务1
$tasks[] = mt_rand(1000,999);//任务2
var_dump($tasks);

//等待所有task结果返回, 超时时间10s
$results = $server->taskWaitMulti($tasks, 10);
if(!isset($results[0])){
    echo '任务1执行超时'.PHP_EOL;
}else{
    echo '任务1执行结果为:'.$results[0].PHP_EOL;
}
if(!isset($results[1])){
    echo '任务2执行超时'.PHP_EOL;
}else{
    echo '任务2执行结果为:'.$results[1].PHP_EOL;
}

taskCo()

并发执行 Task 任务并进行 协程调度 , 用于支持协程环境下的 taskWaitMulti 功能.

Swoole\Server->taskCo(array $tasks, float $timeout=0.5):array
  • $tasks 任务列表, 必须为 数组 . 底层会遍历数组, 将每个元素作为 task 投递到 Task 进程池.
  • $timeout 超时时间, 默认为 0.5 秒. 当规定的时间内任务没有全部完成, 立即终止并返回结果.
  • 任务完成或超时, 返回结果数组. 结果数组中每个任务的结果的顺序与 $tasks 对应, 比如 $tasks[2] 对应的结果为 $result[2] .
  • 某个任务执行失败或超时, 对应的结果数组项为 (bool)false , 如: ``$tasks[2] 失败了那么 $result[2] 的值为 (bool)false`.
  • 最大并发数不得超过1024

调度过程 - $tasks 列表中的每个任务会随机投递到一个 Task 工作进程, 投递完毕后 yield 让出当前协程, 并设置一个 $timeout 秒的定时器. - 在 onFinish 中收集对应的任务结果,, 保存到结果数组中. 判断是否所有任务都返回了结果, 如果为否继续等待 如果为是, 进行 resume 恢复对应协程的运行并清除超时定时器. - 在规定的时间内任务没有全部完成, 定时器先触发, 底层清除等待状态. 将未完成的任务结果标记为 false, 立即 resume 对应的协程.

示例 :

$server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
$server->set([
    'worker_num' => 1,
    'task_worker_num' => 2,//task默认是不启动的, 设置进程数就会启动
]);
$server->on('task', function(Swoole\Server $server, $task_id, $worker_id, $data){
    echo '#'.$server->worker_id.' onTask: worker_id='.$worker_id.', task_id='.$task_id.PHP_EOL;
    if($server->worker_id == 1){
        sleep(1);
    }
    return $data;
});
$server->on('request', function($request, $response)use($server){
    $tasks[0] = 'hello world';
    $tasks[1] = ['data'=> 1234, 'code'=>200];
    //投递任务
    $result = $server->taskCo($tasks, 0.5);
    $response->end('Test End, result:'. var_export($result, true));
});
$server->start();

我们可以用浏览器或者curl去访问url:

finish()

用于在Task进程中通知Worke进程"投递的任务已完成". 该函数可以传递数据给Worker进程.

Swoole\Server->finish(mixed $data)
  • finish 方法可以连续多次调用, Worker 进程会多次触发 onFinish 事件
  • onTask 回调函数中调用过 finish 方法后, 返回数据依然会触发 onFinish 事件
  • Server->finish 是可选的. 如果 Worker 进程不关系执行任务的结果, 不需要回调该函数.
  • onTask 回调函数中返回字符串, 等同于调用 finish

使用 Server->finish 函数必须为 Server 设置 onFinish 回调函数. 此函数只可以用于 Task 进程的 onTask 回调中.

heartbeat()

heartbeat_check_interval 的被动检测不同, 该方法主动检测服务器所有连接, 并找出已经超过约定时间的连接. 如果指定 ifCloseConnection 则自动关闭超时的连接, 未指定则仅返回连接的 fd 数组.

Swoole\Server->heartbeat(bool $ifCloseConnection=true): array|bool
  • 调用成功将返回一个连续的数组, 元素是已关闭的连接的 fd .
  • 调用失败返回 (bool)false .

getLastError()

获取最近一次操作错误的错误码.业务代码中可以根据错误码类型来执行不同的逻辑.

Swoole\Server->getLastError(): int
  • 1001: 连接已经被 Server 端关闭了. 一般出现在执行了 Server->close() 关闭某个连接后仍然调用 Server->send() 向这个连接发送消息的情况下.
  • 1002: 连接已被 Client 端关闭了, Socket 已关闭, 无法发送数据到对端.
  • 1003: 正在执行 close , onClose 回调函数中不得使用 $server->send() .
  • 1004: 连接已关闭
  • 1005: 连接不存在, 传入的 $fd 可能是错误的.
  • 1007: 接收到了超时的数据, TCP 关闭连接后, 可能会有部分数据残留在 unixSocket 缓存区中, 这部分数据会被丢弃.
  • 1008: 发送缓存区已满, 无法执行 send 操作. 出现这个错误表示这个连接的对端无法及时接收数据导致发送缓存区已塞满.
  • 1202: 发哦是哪个的数据超过了 server->buffer_output_size 设置.
  • 9007: 仅在使用 dispatch_mode=3 时出现,, 表示当前没有可用的进程, 可以增加 worker_num 的值以增加 Worker 进程数量.

getSocket()

调用此方法可以得到底层的 socket 句柄, 返回的对象为 sockets 资源句柄.

Swoole\Server->getSocket()

依赖于PHP的sockets扩展, 并且编译Swoole时需要开启 --enable-sockets 选项.

监听端口

  • 使用 listen 方法增加的端口, 可以使用 Swoole\Server\Port 对象提供的 getSocket 方法.
$ports = $server->listen('127.0.0.1', 9502, SWOOLE_SOCKET_TCP);
$socket = $port->getSocket();
  • 使用 socket_set_option 函数可以设置更底层的一些 socket 参数.
$socket = $server->getSocket();
if(!$socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)){
    echo 'Unable to set option on socket:'.socket_strerror(socket_last_error()).PHP_EOL;
}

支持组播(MultiCast)

  • 使用 socket_set_option 设置 MCAST_JOIN_GROUP 参数可以将 Socket 加入组播(MultiCast), 监听网络组播数据包.
$server = new Swoole\Server('127.0.0.1', 9905, SWOOLE_BASE, SWOOLE_SOCK_UDP);
$server->set(['worker_num' => 1]);
$socket = $server->getSocket();
$rt = socket_set_option(
    $socket,
    IPPROTO_IP,
    MCAST_JOIN_GROUP,
    [
        'group'=>'224.10.20.30', //组播地址
        'interface'=>'eth0', //网路接口的名称, 可以为数字或字符串
    ]
);
if($rt === false){
    throw new RuntimeException('Unable to join multicast group');
}
$server->on('packet', function(Swoole\Server $server, $data, $addr){
    $server->sendto($addr['address'], $addr['port'], 'Swoole: '.trim($data));
    var_export($addr, strlen($data));
});
$server->start();

protect()

设置客户端连接为保护状态, 不被心跳线程切断.

Swoole\Server->protect(int $fd, bool $value=true)
  • $value: 设置的状态. true 表示保护状态, false 表示不保护

confirm()

确认连接 , 与 enable_delay_receive 配合使用. 当客户端建立连接后, 并不监听可读时间, 仅触发 onConnect 事件回调, 在 onConnect 回调中执行 confirm 确认连接, 这时服务器才会监听可读事件, 接收来自客户端连接的数据.

在Swoole版本 >= 4.5.0 可用

Swoole\Server->confirm(int $fd)

返回值:

  • 确认成功返回 true
  • $fd对应的连接不存在、已关闭或已经处于监听状态时, 返回 false , 表示确认失败.

用途:

  • 此方法一般用户保护服务器, 避免受到流量过载攻击. 当收到客户端连接时 onConnect 函数触发, 可判断来源IP, 是否允许向服务器发送数据.

示例:

$server = new Swoole\Server('127.0.0.1', 9501);
$server->set([
    'enable_delay_receive'=>true,//必须设置
]);
$server->on('start', function(Swoole\Server $server){
    echo 'Swoole Server start at '.date('Y-m-d H:i:s').PHP_EOL;
});
//监听客户端连接进入事件
$server->on('connect', function(Swoole\Server $server, $fd){
    echo 'Client fd='.$fd.' connect at '.date('Y-m-d H:i:s').PHP_EOL;
    $checked = true;
    //这里可以写检测的业务逻辑, 通过再confirm
    if($checked){
        $rt = $server->confirm($fd);
        if($rt === true){
            echo 'confirm success'.PHP_EOL;
        }else{
            echo 'Error: confirm fail'.PHP_EOL;
        }
    }
});
//监听数据接收事件
$server->on('receive', function(Swoole\Server $server, $fd, $reactor_id, $data){
    //服务器收到连接事件, 向客户端发送回复
    echo 'Receive data from fd='.$fd.': '. trim($data).PHP_EOL;
    $server->send($fd, 'Server: '.trim($data).PHP_EOL);
});
//监听连接关闭事件
$server->on('close', function (Swoole\Server $server, $fd){
    echo 'Client fd='.$fd.' closed at '.date('Y-m-d H:i:s').PHP_EOL;
});
$server->start();

启动服务, 并用telnet连接:

getWorkerId

获取当前 Worker 进程 id (非进程的 PID ), 和 onWOrkerStart 时的 $workerId 一致.

Swoole\Server->getWorkerId(): int|false

Swoole版本>=4.5.0 RC1才可用

getWorkerPID()

获取当前 Worker 进程的 PID .

Swoole\Server->getWorkerPid(): int|false

Swoole版本>=4.5.0 RC1才可用

getWorkerStatus()

获取 Worker 进程状态.

Swoole\Server->getWorkerStatus(int $worker_id): int|false

Swoole版本>=4.5.0 RC1才可用

如果传入的参数 $worker_id 不是 Worker 进程或者进程不存在, 返回 false .

正常返回 Worker 进程状态. 对应的常量和值是:

  • SWOOLE_WORKER_BUSY = 1, 忙碌
  • SWOOLE_WORKER_IDLE = 2, 空闲
  • SWOOLE_WORKER_EXIT = 3, 在reload_async启用的情况下, 同一个worker_id可能有2个进程 一个新的一个老的, 老进程去读到的状态码是EXIT. (自Swoole 4.5.5版本提供)

getManagerPid()

获取当前服务的 Manager 进程 PID

Swoole\Server->getManagerPid(): int

Swoole版本>=4.5.0 RC1才可用

getMasterPid()

获取当前服务的 Master 进程 PID

Swoole\Server->getMasterPid(): int

Swoole版本>=4.5.0 RC1才可用

这几篇关于Swoole\Server方法的文字基本都是参照官方文档手打的, 代码也是重新敲了一遍, 完整的代码都是测试过的(部分不完整的除外).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值