ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ## **Worker** 文档列出属性及静态常量: ``` // $id // $name //全局的eventloop实例,可以向其注册文件描述符的读写事件或者信号事件 //操作系统会将socket可写可读事件通过 select 或者 epoll的方法通知业务进程。业务进程通过调用 select 或者 epoll 来接收这个事件 // ::$globalEvent // connections // reloadable // user // transport // protocol // reusePort // count // ::$logFile // ::$daemonize runAll()中使用了 // ::$stdoutFile // ::$pidFile // ::runAll() // ::stopAll($code = 0, $log = '') // listen() ``` 回调属性 ``` // onWorkerStart=function(Worker $worker){} // onConnect= function(TcpConnection $connection){} // onMessage=function(TcpConnection $connection, $data){} // onClose= function(TcpConnection $connection){} // onError= function(TcpConnection $connection, $code, $msg){} // onBufferFull = function(TcpConnection $connection){} // onBufferDrain = function(TcpConnection $connection){} // onWorkerReload = function(Worker $worker){} // onWorkerStop = function(Worker $worker){} // onWorkerExit = function(Worker $worker, $status, $pid){} ``` ### **示例** ``` require_once __DIR__ . '/vendor/autoload.php'; use Workerman\Worker; use Workerman\Connection\TcpConnection; // 此属性为全局静态属性,用来设置Workerman进程的pid文件路径,如果无特殊需要,注意:建议不要设置此属性,此属性必须在Worker::runAll();运行前设置才有效。windows系统不支持此特性 // 如果不设置,Workerman默认会在与Workerman目录平行的位置自动生成一个pid文件 // 此项设置在监控中比较有用,例如将Workerman的pid文件放入固定的目录中,可以方便一些监控软件读取pid文件,从而监控Workerman进程状态 // Worker::$pidFile = '/var/run/workerman.pid'; // 运行 php start.php start 进入调试模式,这时代码中的echo、var_dump、var_export等函数打印会在终端显示 // 而运行 php start.php start -d则是进入daemon模式,也就是正式上线的运行模式,关闭终端不受影响。 // 如果想daemon方式运行时也能看到echo、var_dump、var_export等函数打印,可以设置Worker::$stdoutFile属性 // 如果启动命令使用了 -d参数,则该属性会自动设置为true。也可以代码中手动设置,如下: Worker::$daemonize = true;//注意:此属性必须在Worker::runAll();运行前设置才有效。windows系统不支持此特性 // 所有的打印输出全部保存在/tmp/stdout.log文件中 Worker::$stdoutFile = '/www/wwwroot/www.dash.vip/stdout.log'; // 指定workerman日志文件位置,此文件记录了workerman自身相关的日志,包括启动、停止等 // 如果没有设置,文件名默认为workerman.log,文件位置位于Workerman的上一级目录中 Worker::$logFile = '/www/wwwroot/www.dash.vip/workerman.log'; // <监听地址> 可以为以下格式 // 是unix套接字,地址为本地一个磁盘路径 // 非unix套接字,地址格式为 <本机ip>:<端口号> // new Worker()没有参数时,不执行任何监听的Worker容器,用来处理一些定时任务 // 运行在主进程,window不能在一个文件里实例多个Worker $worker0 = new Worker('unix:///www/wwwroot/www.dash.vip/my.sock');//$worker = new Worker('unix:///tmp/my_file') $worker = new Worker("tcp://0.0.0.0:2347"); // 启动4个进程对外提供服务,如果是简单业务则设置进程数与cpu合数一样性能最好,如果有数据库操作(数据查询过程中有等待延迟)则设置进程数为cpu核数的3-6倍性能最好 $worker->count = 4; // 设置当前worker是否开启监听端口复用(socket的SO_REUSEPORT选项)。 // workerman多个进程获取连接的方式默认是抢占式的,也就是说当客户端有连接发起时,所有空闲的进程都有机会去获取这个连接,快者先得。到底谁快,是由操作系统内核调度决定的。操作系统可能会优先选取最近一次使用的进程获得cpu使用权,因为cpu寄存器里可能还存在上个进程的上下文信息,这可以减少上下文切换开销。所以当业务足够快的时候或者压测过程中,更容易出现连接集中被某些进程处理的情况,因为这个策略可以避免频繁的进程切换,性能往往是最优的 // 将获取连接的方式从默认的抢占式改为轮询的方式,轮询的方式内核会将连接近似平均的方式分配给所有进程,这样所有的进程将会一起处理请求(php>=7,Windows系统及 Mac OS 不支持此特性) $worker->reusePort = true;//可以提升多进程短连接应用的性能 // 设置当前Worker实例所使用的传输层协议,目前只支持3种(tcp、udp、ssl)。不设置默认为tcp // 设置当前Worker实例的名称,方便运行【php ./start.php start status】命令时识别进程。其中worker_name就是显示设置的实例名称不设置时默认为none $worker->name = 'MyWebsocketWorker'; // 将"tcp://0.0.0.0:2347"应用层 协议改为Http $worker->protocol = 'Workerman\\Protocols\\Http'; // 设置当前Worker实例所使用的传输层协议,目前只支持3种(tcp、udp、ssl)。不设置默认为tcp $worker->transport = 'udp';// 传输层使用udp协议 // 设置当前Worker实例以哪个用户运行。此属性只有当前用户为root时才能生效。不设置时默认以当前用户运行,建议$user设置权限较低的用户,例如www-data、apache、nobody等。 // 注意:此属性必须在Worker::runAll();运行前设置才有效。windows系统不支持此特性。 $worker->user = 'www-data'; // 执行php start.php reload时会向所有子进程发送reload信号(SIGUSR1)。 // 默认true时,子进程收到reload信号后会自动退出然后主进程会自动拉起一个新的进程,一般用于更新业务代码 // 当进程$reloadable为false时,收到reload信号后子进程收到reload信号不执行重启,只会触发 onWorkerReload $worker->reloadable = false; //设置reloadable属性为false,即子进程收到reload信号不执行重启,只会触发 onWorkerReload $worker->onWorkerReload = function(Worker $worker){}; /* 当客户端与Workerman建立连接时(TCP三次握手完成后)触发的回调函数。每个连接只会触发一次onConnect回调 注意:onConnect事件仅仅代表客户端与Workerman完成了TCP三次握手,这时客户端还没有发来任何数据,此时除了通过$connection->getRemoteIp()获得对方ip,没有其他可以鉴别客户端的数据或者信息,所以在onConnect事件里无法确认对方是谁。要想知道对方是谁,需要客户端发送鉴权数据,例如某个token或者用户名密码之类,在onMessage回调里做鉴权。 由于udp是无连接的,所以当使用udp时不会触发onConnect回调,也不会触发onClose回调 */ $worker->onConnect = function(TcpConnection $connection) { echo "new connection from ip " . $connection->getRemoteIp() . "\n"; }; // Worker子进程启动时的回调函数,每个子进程启动时都会执行 $worker->onWorkerStart = function(Worker $worker){ echo "Worker {$worker->id} starting...\n"; // 实例化Worker后执行监听,在Worker进程启动后动态创建新的Worker实例,能够实现同一个进程监听多个端口,支持多种协议。需要注意的是用这种方法只是在当前进程增加监听,并不会动态创建新的进程,也不会触发onWorkerStart方法 // 例如一个http Worker启动后实例化一个websocket Worker,那么这个进程即能通过http协议访问,又能通过websocket协议访问。由于websocket Worker和http Worker在同一个进程中,所以它们可以访问共同的内存变量,共享所有socket连接。可以做到接收http请求,然后操作websocket客户端完成向客户端推送数据类似的效果 // 如果PHP版本<=7.0,则不支持在多个子进程中实例化相同端口的Worker。例如A进程创建了监听2016端口的Worker,那么B进程就不能再创建监听2016端口的Worker,否则会报Address already in use错误 /** * 4个进程启动的时候都创建2016端口的Worker * 当执行到worker->listen()时会报Address already in use错误 * 如果worker->count=1则不会报错 */ $inner_worker = new Worker('http://0.0.0.0:2016'); $inner_worker->onMessage = function(TcpConnection $connection, $data){}; // 执行监听。这里会报Address already in use错误 $inner_worker->listen(); // 如果您的PHP版本>=7.0,可以设置Worker->reusePort=true, 这样可以做到多个子进程创建相同端口的Worker $inner_worker = new Worker('http://0.0.0.0:2016'); // 设置端口复用,可以创建监听相同端口的Worker(需要PHP>=7.0) $inner_worker->reusePort = true; $inner_worker->onMessage = function(TcpConnection $connection, $data){}; // 执行监听。正常监听不会报错 $inner_worker->listen(); }; /* 每个连接都有一个单独的应用层发送缓冲区,如果客户端接收速度小于服务端发送速度,数据会在应用层缓冲区暂存,如果缓冲区满(超过TcpConnection::$maxSendBufferSize上限)则会触发onBufferFull回调 设置所有连接的默认应用层发送缓冲区大小,单位字节:TcpConnection::$defaultMaxSendBufferSize = 2*1024*1024; 也可以为当前连接动态设置缓冲区大小(默认值为1MB)例如:$connection->maxSendBufferSize = 102400; */ $worker->onBufferFull = function(TcpConnection $connection) { echo "bufferFull and do not send again\n"; }; /* 当发生onBufferFull事件时,开发者一般需要采取措施,例如停止向对端发送数据,等待发送缓冲区的数据被发送完毕(onBufferDrain事件)等 该回调在应用层发送缓冲区数据全部发送完毕后触发。一般与onBufferFull配合使用,例如在onBufferFull时停止向对端继续send数据,在onBufferDrain恢复写入数据。 */ $worker->onBufferDrain = function(TcpConnection $connection) { echo "buffer drain and continue send\n"; }; // 当客户端的连接上发生错误时触发 $worker->onError = function(TcpConnection $connection, $code, $msg) { echo "error $code $msg\n"; }; /* 当客户端通过连接发来数据时(Workerman收到数据时)触发的回调函数,赋值过程运行在主进程 $data:客户端连接上发来的数据,如果Worker指定了协议,则$data是对应协议decode(解码)了的数据。数据类型与协议decode()实现有关,websocket text frame 为字符串,HTTP协议为 Workerman\Protocols\Http\Request对象。 */ $worker->onMessage = function(TcpConnection $connection, $data) { $worker->id;//进程id编号 编号从0计数,最大值为($tcp_worker->count)-1 // 当进程收到SIGALRM信号时,打印输出一些信息 Worker::$globalEvent->add(SIGALRM, EventInterface::EV_SIGNAL, function() { echo "Get signal SIGALRM\n"; }); // 这部分运行在子进程 // $connection->send('hello ' . $data); // 定时,每10秒一次 Timer::add(10, function()use($worker) { // 遍历当前进程所有的客户端连接,发送当前服务器的时间 foreach($worker->connections as $connection) { $connection->send(time()); } }); // 已经处理请求数 static $request_count = 0; // 如果请求数达到1000 if(++$request_count >= MAX_REQUEST) { /* * 退出当前进程,主进程会立刻重新启动一个全新进程补充上来 * 从而完成进程重启 */ Worker::stopAll(); } }; // 当客户端连接与Workerman断开时触发的回调函数。不管连接是如何断开的,只要断开就会触发onClose。每个连接只会触发一次onClose // 由于udp是无连接的,所以当使用udp时不会触发onConnect回调,也不会触发onClose回调 $worker->onClose = function(TcpConnection $connection) { echo "connection closed\n"; }; // 进程关闭(停止)时触发 $worker->onWorkerStop = function($worker) { //通知运维人员 //sc('WebSocket服务进程退出,如非定时重启,请检查!'); }; // 当工作进程退出时触发 $worker->onWorkerExit=function($worker, $status, $pid){}; // 一个文件里同时new 多个 Worker,那么runAll也会运行多个Worker实例 // 注意windows里同一个文件不能同时new 多个Worker Worker::runAll(); ``` ![](https://img.kancloud.cn/b2/18/b218e94fe6cd8ff5a127f4f6df12bcd5_860x160.png) ``` // VERSION workman版本 // STATUS_STARTING 开始状态 // STATUS_RUNNING 运行状态 // STATUS_SHUTDOWN 关闭状态 // STATUS_RELOADING 重新加载状态 // DEFAULT_BACKLOG 默认backlog。Backlog是挂起连接队列的最大长度 // MAX_UDP_PACKAGE_SIZE 最大udp包大小 // UI_SAFE_LENGTH 相邻列(columns)的安全距离 // group Unix进程组需要适当的权限(通常是root) // stopping 进程是否停止默认false // ::$statusFile 用于存储主进程状态文件的文件吗默认'' // ::$onMasterReload 当主进程收到重载信号时触发 // ::$onMasterStop 主进程终止时触发 // ::$eventLoopClass // ::$processTitle 进程标题 // ::$stopTimeout 在向子进程发送停止命令stopTimeout设置的秒后,如果子进程仍在运行,则强制终止 // ::checkSapiEnv() 检查sapi // ::init() runAll()中使用了 // ::reloadAllWorkers() runAll()中使用了 重新加载所有工作实例 // ::resetStd() runAll()中使用了 重定向标准输入和输出 // ::getAllWorkers() 获取所有worker实例 // ::getEventLoop() 获取全局事件循环实例 // ::getMainSocket() 获取主套接字资源 // ::getUiColumns() 获取终端中显示的UI列 // ::getSingleLineTotalLength() 获取ui的单行总长度 // ::signalHandler($signal) 信号处理器 // ::getStartFilesForWindows() 获取windows的启动文件$argv // ::forkOneWorkerForWindows($start_file) 分叉(Fork)一个worker进程 // ::checkWorkerStatusForWindows() 检查windows的worker状态 // ::checkIfChildRunning() 检查子进程是否真的在运行 // ::getStatus() 获取进程状态 // ::getGracefulStop() 如果优雅地停止 // ::checkErrors() 当前进程退出时检查错误 // ::log($msg) 记录日志到static::$logFile文件 // ::safeEcho($msg, $decorated = false) 安全的输出(阻止输出错误信息到流,即只输出$msg到流fwrite($stream, $msg)) // __construct($socket_name = '', array $context_option = array()) // setUserAndGroup() 为当前进程设置unix用户和组 // unlisten() 停止监听 // pauseAccept() 暂停接受新连接 // resumeAccept() 恢复并接受新连接 // getSocketName() 获取套接字(Socket)名称 // run() 运行工作实例 // stop() 停止当前工作实例 // acceptConnection($socket) 接受连接 // acceptUdpConnection($socket) 对于udp包 ``` 般来说在`Worker::runAll();`调用前运行的代码都是在主进程运行的,onXXX回调运行的代码都属于子进程。注意写在`Worker::runAll();`后面的代码永远不会被执行 ## **new Worker()没有参数时,不执行任何监听的Worker容器,用来处理一些定时任务** ``` use \Workerman\Worker; use \Workerman\Timer; require_once __DIR__ . '/vendor/autoload.php'; $task = new Worker(); $task->onWorkerStart = function($task) { // 每2.5秒执行一次 $time_interval = 2.5; Timer::add($time_interval, function() { echo "task run\n"; }); }; // 运行worker Worker::runAll(); ``` ## **配置ssl证书** ``` use Workerman\Worker; use Workerman\Connection\TcpConnection; require_once __DIR__ . '/vendor/autoload.php'; // 证书最好是申请的证书 $context = array( 'ssl' => array( 'local_cert' => '/etc/nginx/conf.d/ssl/server.pem', // 也可以是crt文件 'local_pk' => '/etc/nginx/conf.d/ssl/server.key', ) ); // 这里设置的是websocket协议 $worker = new Worker('websocket://0.0.0.0:4431', $context); // 设置transport开启ssl,websocket+ssl即wss $worker->transport = 'ssl'; $worker->onMessage = function(TcpConnection $con, $msg) { $con->send('ok'); }; Worker::runAll(); ```