当前位置: 首页 > 文档资料 > Swoole 中文文档 >

方法

优质
小牛编辑
146浏览
2023-12-01

__construct()

创建一个异步IO的Server对象。

Swoole\Server(string $host = '0.0.0.0', int $port = 0, int $mode = SWOOLE_PROCESS, int $sockType = SWOOLE_SOCK_TCP): \Swoole\Server
  • 参数

    • string $host

      • 功能:指定监听的ip地址
      • 默认值:无
      • 其它值:无

      !> IPv4使用 127.0.0.1表示监听本机,0.0.0.0表示监听所有地址
      IPv6使用::1表示监听本机,:: (相当于0:0:0:0:0:0:0:0) 表示监听所有地址

    • int $port

      • 功能:指定监听的端口,如9501
      • 默认值:无
      • 其它值:无

      !> 如果 $sockType 值为 UnixSocket Stream/Dgram,此参数将被忽略
      监听小于1024端口需要root权限
      如果此端口被占用 server->start 时会失败

    • int $mode

    • int $sockType

      • 功能:指定这组Server的类型
      • 默认值:无
      • 其它值:
        • SWOOLE_TCP/SWOOLE_SOCK_TCP tcp ipv4 socket
        • SWOOLE_TCP6/SWOOLE_SOCK_TCP6 tcp ipv6 socket
        • SWOOLE_UDP/SWOOLE_SOCK_UDP udp ipv4 socket
        • SWOOLE_UDP6/SWOOLE_SOCK_UDP6 udp ipv6 socket
        • SWOOLE_UNIX_DGRAM unix socket dgram
        • SWOOLE_UNIX_STREAM unix socket stream

      !> 使用 $sock_type | SWOOLE_SSL 可以启用 SSL 隧道加密。启用 SSL 后必须配置 ssl_key_filessl_cert_file

  • 示例

<?php
$server = new \Swoole\Server(string $host, int $port = 0, int $mode = SWOOLE_PROCESS, int $sockType = SWOOLE_SOCK_TCP);

// 您可以混合使用UDP/TCP,同时监听内网和外网端口,多端口监听参考 addlistener小节。
$server->addlistener("127.0.0.1", 9502, SWOOLE_SOCK_TCP); // 添加 TCP
$server->addlistener("192.168.1.100", 9503, SWOOLE_SOCK_TCP); // 添加 Web Socket
$server->addlistener("0.0.0.0", 9504, SWOOLE_SOCK_UDP); // UDP
$server->addlistener("/var/run/myserv.sock", 0, SWOOLE_UNIX_STREAM); //UnixSocket Stream
$server->addlistener("127.0.0.1", 9502, SWOOLE_SOCK_TCP | SWOOLE_SSL); //TCP + SSL

$port = $server->addListener("0.0.0.0", 0, SWOOLE_SOCK_TCP); // 系统随机分配端口,返回值为随机分配的端口
echo $port->port;

set()

用于设置运行时的各项参数。服务器启动后通过$serv->setting来访问Server->set方法设置的参数数组。

Swoole\Server->set(array $setting): void;

!> Server->set 必须在 Server->start 前调用,具体每个配置的意义请参考此节

  • 示例

    $server->set(array(
        'reactor_num'   => 2,     // reactor thread num
        'worker_num'    => 4,     // worker process num
        'backlog'       => 128,   // listen backlog
        'max_request'   => 50,
        'dispatch_mode' => 1,
    ));

on()

注册Server的事件回调函数。

Swoole\Server->on(string $event, mixed $callback): void

!> 重复调用on方法时会覆盖上一次的设定

  • 参数

    • string $event

      • 功能:回调事件名称
      • 默认值:无
      • 其它值:无

      !> 大小写不敏感,具体有哪些事件回调参考此节,事件名称字符串不要加on

    • int $callback

      • 功能:回调函数
      • 默认值:无
      • 其它值:无

      !> 可以是函数名的字符串,类静态方法,对象方法数组,匿名函数 参考此节

  • 示例

  $server = new Swoole\Server("127.0.0.1", 9501);
  $server->on('connect', function ($server, $fd){
      echo "Client:Connect.\n";
  });
  $server->on('receive', function ($server, $fd, $reactor_id, $data) {
      $server->send($fd, 'Swoole: '.$data);
      $server->close($fd);
  });
  $server->on('close', function ($server, $fd) {
      echo "Client: Close.\n";
  });
  $server->start();

addListener()

增加监听的端口。业务代码中可以通过调用 Server->getClientInfo 来获取某个连接来自于哪个端口。

Swoole\Server->addListener(string $host, int $port, $type = SWOOLE_SOCK_TCP): bool|Swoole\Server\Port

!> 监听1024以下的端口需要root权限
主服务器是WebSocketHttp协议,新监听的TCP端口默认会继承主Server的协议设置。必须单独调用set方法设置新的协议才会启用新协议 查看详细说明

  • 参数

    • string $host

      • 功能:与 __construct()$host 相同
      • 默认值:与 __construct()$host 相同
      • 其它值:与 __construct()$host 相同
    • int $port

      • 功能:与 __construct()$port 相同
      • 默认值:与 __construct()$port 相同
      • 其它值:与 __construct()$port 相同
    • int $sockType

      • 功能:与 __construct()$sockType 相同
      • 默认值:与 __construct()$sockType 相同
      • 其它值:与 __construct()$sockType 相同

      !> -Unix Socket模式下$host参数必须填写可访问的文件路径,$port参数忽略
      -Unix Socket模式下,客户端$fd将不再是数字,而是一个文件路径的字符串
      -Linux系统下监听IPv6端口后使用IPv4地址也可以进行连接

listen()

此方法是 addlistener 的别名。

Swoole\Server->listen(string $host, int $port, $type = SWOOLE_SOCK_TCP): bool|Swoole\Server\Port

addProcess()

添加一个用户自定义的工作进程。此函数通常用于创建一个特殊的工作进程,用于监控、上报或者其他特殊的任务。

Swoole\Server->addProcess(Swoole\Process $process): bool

!> 不需要执行start。在Server启动时会自动创建进程,并执行指定的子进程函数

  • 参数

    • Swoole\Process
      • 功能:Swoole\Process 对象
      • 默认值:无
      • 其它值:无
  • 注意

    !> -创建的子进程可以调用$server对象提供的各个方法,如getClientList/getClientInfo/stats
    -在Worker/Task进程中可以调用$process提供的方法与子进程进行通信
    -在用户自定义进程中可以调用$server->sendMessageWorker/Task进程通信
    -用户进程内不能使用Server->task/taskwait接口
    -用户进程内可以使用Server->send/close等接口
    -用户进程内应当进行while(true)(如下边的示例)或EventLoop循环(例如创建个定时器),否则用户进程会不停地退出重启

  • 提示

    • 生命周期

      ?> -用户进程的生存周期与MasterManager 是相同的,不会受到 reload 影响
      -用户进程不受reload指令控制,reload时不会向用户进程发送任何信息
      -在shutdown关闭服务器时,会向用户进程发送SIGTERM信号,关闭用户进程
      -自定义进程会托管到Manager进程,如果发生致命错误,Manager进程会重新创建一个

  • 示例

    $server = new Swoole\Server('127.0.0.1', 9501);
    
    /**
     * 用户进程实现了广播功能,循环接收unixSocket的消息,并发给服务器的所有连接
     */
    $process = new Swoole\Process(function ($process) use ($server) {
        $socket = $process->exportSocket();
        while (true) {
            $msg = $socket->recv();
            foreach ($server->connections as $conn) {
                $server->send($conn, $msg);
            }
        }
    }, false, 2, 1);
    
    $server->addProcess($process);
    
    $server->on('receive', function ($serv, $fd, $reactor_id, $data) use ($process) {
        //群发收到的消息
        $socket = $process->exportSocket();
        $socket->send($data);
    });
    
    $server->start();

    参考Process进程间通讯章节

start()

启动服务器,监听所有TCP/UDP端口。

Swoole\Server->start(): bool
  • 参数

!> 提示:以下以 SWOOLE_PROCESS 模式为例

  • 提示

    • 启动成功后会创建worker_num+2个进程。Master进程+Manager进程+serv->worker_numWorker进程。
    • 启动失败会立即返回false
    • 启动成功后将进入事件循环,等待客户端连接请求。start方法之后的代码不会执行
    • 服务器关闭后,start函数返回true,并继续向下执行
    • 设置了task_worker_num会增加相应数量的 Task进程
    • 方法列表中start之前的方法仅可在start调用前使用,在start之后的方法仅可在onWorkerStartonReceive等事件回调函数中使用
  • 扩展

    • Master 主进程

      • 主进程内有多个Reactor线程,基于epoll/kqueue进行网络事件轮询。收到数据后转发到Worker进程去处理
    • Manager 进程

      • 对所有Worker进程进行管理,Worker进程生命周期结束或者发生异常时自动回收,并创建新的Worker进程
    • Worker 进程

      • 对收到的数据进行处理,包括协议解析和响应请求。未设置worker_num,底层会启动与CPU数量一致的Worker进程。
      • 启动失败扩展内会抛出致命错误,请检查php error_log的相关信息。errno={number}是标准的Linux Errno,可参考相关文档。
      • 如果开启了log_file设置,信息会打印到指定的Log文件中。
  • 启动失败常见错误

  • bind端口失败,原因是其他进程已占用了此端口

  • 未设置必选回调函数,启动失败

  • PHP代码存在致命错误,请检查PHP错误信息php_errors.log

  • 执行ulimit -c unlimited,打开core dump,查看是否有段错误

  • 关闭daemonize,关闭log,使错误信息可以打印到屏幕

reload()

安全地重启所有Worker/Task进程。

Swoole\Server->reload(bool $only_reload_taskworkrer = false): bool

!> 例如:一台繁忙的后端服务器随时都在处理请求,如果管理员通过kill进程方式来终止/重启服务器程序,可能导致刚好代码执行到一半终止。
这种情况下会产生数据的不一致。如交易系统中,支付逻辑的下一段是发货,假设在支付逻辑之后进程被终止了。会导致用户支付了货币,但并没有发货,后果非常严重。
Swoole提供了柔性终止/重启的机制,管理员只需要向Server发送特定的信号,ServerWorker进程可以安全的结束。参考 如何正确的重启服务

  • 参数

    • bool $only_reload_taskworkrer
      • 功能:是否仅重启 Task进程
      • 默认值:false
      • 其它值:无

!> -reload有保护机制,当一次reload正在进行时,收到新的重启信号会丢弃
-如果设置了user/groupWorker进程可能没有权限向master进程发送信息,这种情况下必须使用root账户,在shell中执行kill指令进行重启
-reload指令对 addProcess 添加的用户进程无效

  • 扩展

    • 发送信号

      • SIGTERM: 向主进程/管理进程发送此信号服务器将安全终止
      • 在PHP代码中可以调用$serv->shutdown()完成此操作
      • SIGUSR1: 向主进程/管理进程发送SIGUSR1信号,将平稳地restart所有Worker进程
      • SIGUSR2: 向主进程/管理进程发送SIGUSR2信号,将平稳地重启所有Task进程
      • 在PHP代码中可以调用$serv->reload()完成此操作
        # 重启所有worker进程
        kill -USR1 主进程PID
      
        # 仅重启task进程
        kill -USR2 主进程PID

      参考:Linux信号列表

    • Process模式

      Process启动的进程中,来自客户端的TCP连接是在Master进程内维持的,worker进程的重启和异常退出,不会影响连接本身。

    • Base模式

      Base模式下,客户端连接直接维持在Worker进程中,因此reload时会切断所有连接。

    !> Base模式不支持 reload Task进程

    • Reload有效范围

      Reload操作只能重新载入Worker进程启动后加载的PHP文件,使用get_included_files函数来列出哪些文件是在WorkerStart之前就加载的PHP文件,在此列表中的PHP文件,即使进行了reload操作也无法重新载入。要关闭服务器重新启动才能生效。

      $serv->on('WorkerStart', function(Swoole\Server $server, int $workerId) {
          var_dump(get_included_files()); //此数组中的文件表示进程启动前就加载了,所以无法reload
      });
    • APC/OpCache

      如果PHP开启了APC/OpCachereload重载入时会受到影响,有2种解决方案

      • 打开APC/OpCachestat检测,如果发现文件更新APC/OpCache会自动更新OpCode
      • onWorkerStart中加载文件(require、include等函数)之前执行apc_clear_cacheopcache_reset刷新OpCode缓存
  • 注意

!> -平滑重启只对onWorkerStartonReceive等在Worker进程中include/require的PHP文件有效
-Server启动前就已经include/require的PHP文件,不能通过平滑重启重新加载
-对于Server的配置即$serv->set()中传入的参数设置,必须关闭/重启整个Server才可以重新加载
-Server可以监听一个内网端口,然后可以接收远程的控制命令,去重启所有Worker进程

stop()

使当前Worker进程停止运行,并立即触发onWorkerStop回调函数。

Swoole\Server->stop(int $workerId = -1, bool $waitEvent = false): bool
  • 参数

    • int $workerId

      • 功能:指定 worker id
      • 默认值:-1
      • 其它值:无
    • bool $waitEvent

      • 功能:控制退出策略,false表示立即退出,true表示等待事件循环为空时再退出
      • 默认值:false
      • 其它值:true
  • 提示

    !> -异步IO服务器在调用stop退出进程时,可能仍然有事件在等待。比如使用了Swoole\MySQL->query,发送了SQL语句,但还在等待MySQL服务器返回结果。这时如果进程强制退出,SQL的执行结果就会丢失了。
    -设置$waitEvent = true后,底层会使用异步安全重启策略。先通知Manager进程,重新启动一个新的Worker来处理新的请求。当前旧的Worker会等待事件,直到事件循环为空或者超过max_wait_time后,退出进程,最大限度的保证异步事件的安全性。

shutdown()

关闭服务。

Swoole\Server->shutdown(): void
  • 参数

  • 提示

    • 此函数可以用在Worker进程内
    • 向主进程发送SIGTERM也可以实现关闭服务
      kill -15 主进程PID

tick()

添加tick定时器,可以自定义回调函数。此函数是 Swoole\Timer::tick 的别名。

Swoole\Server->tick(int $millisecond, mixed $callback): void
  • 参数

    • int $millisecond

      • 功能:间隔时间【毫秒】
      • 默认值:无
      • 其它值:无
    • mixed $callback

      • 功能:回调函数
      • 默认值:无
      • 其它值:无
  • 注意

!> -Worker进程结束运行后,所有定时器都会自动销毁
-tick/after定时器不能在Server->start之前使用

  • 示例

    • onReceive 中使用

      function onReceive(Swoole\Server $server, int $fd, int $reactorId, mixed $data)
      {
          $server->tick(1000, function () use ($server, $fd) {
              $server->send($fd, "hello world");
          });
      }
    • onWorkerStart 中使用

      function onWorkerStart(Swoole\Server $server, int $workerId)
      {
          if (!$server->taskworker) {
              $server->tick(1000, function ($id) {
                  var_dump($id);
              });
          } else {
              //task
              $server->tick(1000);
          }
      }

after()

添加一个一次性定时器,执行完成后就会销毁。此函数是 Swoole\Timer::after 的别名。

Swoole\Server->after(int $millisecond, mixed $callback)
  • 参数

    • int $millisecond

      • 功能:执行时间【毫秒】
      • 默认值:无
      • 其它值:无
      • 版本影响:
        • Swoole 4.2.10 以下版本最大不得超过 86400000
    • mixed $callback

      • 功能:回调函数,必须是可以调用的,callback 函数不接受任何参数
      • 默认值:无
      • 其它值:无
  • 注意

!> -定时器的生命周期是进程级的,当使用reloadkill重启关闭进程时,定时器会全部被销毁
-如果有某些定时器存在关键逻辑和数据,请在onWorkerStop回调函数中实现,或参考 如何正确的重启服务

defer()

延后执行一个函数,是 Event::defer 的别名。

Swoole\Server->defer(callable $callback): void
  • 参数

    • callable $callback
      • 功能:回调函数【必填】,可以是可执行的函数变量,可以是字符串、数组、匿名函数
      • 默认值:无
      • 其它值:无
  • 注意

!> -底层会在EventLoop循环完成后执行此函数。此函数的目的是为了让一些PHP代码延后执行,程序优先处理其他的IO事件。比如某个回调函数有CPU密集计算又不是很着急,可以让进程处理完其他的事件再去CPU密集计算
-底层不保证defer的函数会立即执行,如果是系统关键逻辑,需要尽快执行,请使用after定时器实现
-在onWorkerStart回调中执行defer时,必须要等到有事件发生才会回调

  • 示例
  function query($server, $db) {
      $server->defer(function() use ($db) {
          $db->close();
      });
  }

clearTimer()

清除tick/after定时器,此函数是 Swoole\Timer::clear 的别名。

Swoole\Server->clearTimer(int $timerId): bool
  • 参数

    • int $timerId
      • 功能:指定定时器id
      • 默认值:无
      • 其它值:无
  • 注意

!> clearTimer仅可用于清除当前进程的定时器

  • 示例
  $timerId = $server->tick(1000, function ($id) use ($server) {
      $server->clearTimer($id);//$id是定时器的id
  });

close()

关闭客户端连接。

Swoole\Server->close(int $fd, bool $reset = false): bool
  • 参数

    • int $fd
      • 功能:指定关闭的 fd (文件描述符)
      • 默认值:无
      • 其它值:无
    • bool $reset
      • 功能:设置为true会强制关闭连接,丢弃发送队列中的数据
      • 默认值:false
      • 其它值:true
  • 注意

!> -Server主动close连接,也一样会触发onClose事件
-不要在close之后写清理逻辑。应当放置到onClose回调中处理
-HTTP\Serverfd在上层回调方法的response中获取

  • 示例

    $server->on('request', function ($request, $response) use ($server) {
        $server->close($response->fd);
    });

send()

向客户端发送数据。

Swoole\Server->send(int $fd, string $data, int $serverSocket  = -1): bool
  • 参数

    • int $fd
      • 功能:指定客户端的文件描述符
      • 默认值:无
      • 其它值:无
    • string $data
      • 功能:发送的数据,TCP协议最大不得超过2M,可修改 buffer_output_size 改变允许发送的最大包长度
      • 默认值:无
      • 其它值:无
    • int $serverSocket
      • 功能:向UnixSocket DGRAM对端发送数据时需要此项参数,TCP客户端不需要填写
      • 默认值:-1
      • 其它值:无
  • 提示

    !> 发送过程是异步的,底层会自动监听可写,将数据逐步发送给客户端,也就是说不是send返回后对端就收到数据了。

    • 安全性

      • send操作具有原子性,多个进程同时调用send向同一个TCP连接发送数据,不会发生数据混杂
    • 长度限制

      • 如果要发送超过2M的数据,可以将数据写入临时文件,然后通过sendfile接口进行发送
      • 通过设置 buffer_output_size 参数可以修改发送长度的限制
      • 在发送超过8K的数据时,底层会启用Worker进程的共享内存,需要进行一次Mutex->lock操作
    • 缓存区

      • Worker进程的unixSocket缓存区已满时,发送8K数据将启用临时文件存储
      • 如果连续向同一个客户端发送大量数据,客户端来不及接收会导致Socket内存缓存区塞满,Swoole底层会立即返回false,false时可以将数据保存到磁盘,等待客户端收完已发送的数据后再进行发送
    • 协程调度

      • 在协程模式开启了send_yield情况下send遇到缓存区已满时会自动挂起,当数据被对端读走一部分后恢复协程,继续发送数据。
    • UnixSocket

      $server->on("packet", function (Swoole\Server $server, $data, $addr){
          $server->send($addr['address'], 'SUCCESS', $addr['server_socket']);
      });

sendfile()

发送文件到TCP客户端连接。

Swoole\Server->sendfile(int $fd, string $filename, int $offset = 0, int $length = 0): bool
  • 参数

    • int $fd

      • 功能:指定客户端的文件描述符
      • 默认值:无
      • 其它值:无
    • int $filename

      • 功能:要发送的文件路径,如果文件不存在会返回false
      • 默认值:无
      • 其它值:无
    • int $offset

      • 功能:指定文件偏移量,可以从文件的某个位置起发送数据
      • 默认值:0 【默认为0,表示从文件头部开始发送】
      • 其它值:无
    • int $length

      • 功能:指定发送的长度
      • 默认值:文件尺寸
      • 其它值:无
  • 注意

!> 此函数与Server->send都是向客户端发送数据,不同的是sendfile的数据来自于指定的文件

sendto()

向任意的客户端IP:PORT发送UDP数据包。

Swoole\Server->sendto(string $ip, int $port, string $data, int $serverSocket = -1): bool
  • 参数

    • string $ip

      • 功能:指定客户端 ip
      • 默认值:无
      • 其它值:无

      ?> $ipIPv4IPv6字符串,如192.168.1.102。如果IP不合法会返回错误

    • int $port

      • 功能:指定客户端 port
      • 默认值:无
      • 其它值:无

      ?> $port1-65535的网络端口号,如果端口错误发送会失败

    • string $data

      • 功能:要发送的数据内容,可以是文本或者二进制内容
      • 默认值:无
      • 其它值:无
    • string $serverSocket

      • 功能:指定使用哪个端口发送数据包
      • 默认值:无
      • 其它值:无

      ?> 服务器可能会同时监听多个UDP端口,参考多端口监听,此参数可以指定使用哪个端口发送数据包

  • 注意

!> 必须监听了UDP的端口,才可以使用向IPv4地址发送数据
必须监听了UDP6的端口,才可以使用向IPv6地址发送数据

  • 示例
  //向IP地址为220.181.57.216主机的9502端口发送一个hello world字符串。
  $server->sendto('220.181.57.216', 9502, "hello world");
  //向IPv6服务器发送UDP数据包
  $server->sendto('2600:3c00::f03c:91ff:fe73:e98f', 9501, "hello world");

sendwait()

同步地向客户端发送数据。

Swoole\Server->sendwait(int $fd, string $data): bool
  • 参数

    • int $fd

      • 功能:指定客户端的文件描述符
      • 默认值:无
      • 其它值:无
    • string $data

      • 功能:指定客户端的文件描述符
      • 默认值:无
      • 其它值:无
  • 提示

    • 有一些特殊的场景,Server需要连续向客户端发送数据,而Server->send数据发送接口是纯异步的,大量数据发送会导致内存发送队列塞满。

    • 使用Server->sendwait就可以解决此问题,Server->sendwait会等待连接可写。直到数据发送完毕才会返回。

  • 注意

!> sendwait目前仅可用于SWOOLE_BASE模式
sendwait只用于本机或内网通信,外网连接请勿使用sendwait,在enable_coroutine=>true(默认开启)的时候也不要用这个函数,会卡死其他协程,只有同步阻塞的服务器才可以用。

sendMessage()

向任意Worker进程或者 Task进程发送消息。在非主进程和管理进程中可调用。收到消息的进程会触发onPipeMessage事件。

Swoole\Server->sendMessage(string $message, int $workerId): bool
  • 参数

    • string $message

      • 功能:为发送的消息数据内容,没有长度限制,但超过8K时会启动内存临时文件
      • 默认值:无
      • 其它值:无
    • int $workerId

      • 功能:目标进程的ID,范围参考$worker_id
      • 默认值:无
      • 其它值:无
  • 提示

  • 注意

!> - 如果sendMessage()异步IO的,如果对端进程因为种种原因不接收数据,千万不要一直sendMessage(),会导致占用大量的内存资源,可以做个应答机制,对端不回应就不要发了;
-MacOS/FreeBSD下超过2K就会使用临时文件存储;
-使用sendMessage必须注册onPipeMessage事件回调函数;
-设置了 task_ipc_mode = 3 将无法使用sendMessage向特定的task进程发送消息。

  • 示例
  $server = new Swoole\Server("0.0.0.0", 9501);

  $server->set(array(
      'worker_num'      => 2,
      'task_worker_num' => 2,
  ));
  $server->on('pipeMessage', function ($server, $src_worker_id, $data) {
      echo "#{$server->worker_id} message from #$src_worker_id: $data\n";
  });
  $server->on('task', function ($server, $task_id, $reactor_id, $data) {
      var_dump($task_id, $reactor_id, $data);
  });
  $server->on('finish', function ($server, $fd, $reactor_id) {

  });
  $server->on('receive', function (Swoole\Server $server, $fd, $reactor_id, $data) {
      if (trim($data) == 'task') {
          $server->task("async task coming");
      } else {
          $worker_id = 1 - $server->worker_id;
          $server->sendMessage("hello task process", $worker_id);
      }
  });

  $server->start();

exist()

检测fd对应的连接是否存在。

Swoole\Server->exist(int $fd): bool
  • 参数

    • int $fd
      • 功能:文件描述符
      • 默认值:无
      • 其它值:无
  • 提示

    • 此接口是基于共享内存计算,没有任何IO操作

pause()

停止接收数据。

Swoole\Server->pause(int $fd)
  • 参数

    • int $fd
      • 功能:指定文件描述符
      • 默认值:无
      • 其它值:无
  • 提示

    • 调用此函数后会将连接从EventLoop中移除,不再接收客户端数据。
    • 此函数不影响发送队列的处理
    • 只能在SWOOLE_PROCESS模式下,调用pause后,可能有部分数据已经到达Worker进程,因此仍然可能会触发onReceive事件

resume()

恢复数据接收。与pause方法成对使用。

Swoole\Server->resume(int $fd)
  • 参数

    • int $fd
      • 功能:指定文件描述符
      • 默认值:无
      • 其它值:无
  • 提示

    • 调用此函数后会将连接重新加入到EventLoop中,继续接收客户端数据

getClientInfo()

获取连接的信息,别名是Swoole\Server->connection_info()

Swoole\Server->getClientInfo(int $fd, int $extraData, bool $ignoreError = false): bool|array
  • 参数

    • int $fd

      • 功能:指定文件描述符
      • 默认值:无
      • 其它值:无
    • int $extraData

      • 功能:扩展信息,保留参数,目前无任何效果
      • 默认值:无
      • 其它值:无
    • bool $extraData

      • 功能:是否忽略错误,如果设置为true,即使连接关闭也会返回连接的信息
      • 默认值:无
      • 其它值:无
  • 提示

    • 客户端证书

      • 仅在onConnect触发的进程中才能获取到证书
      • 格式为x509格式,可使用openssl_x509_parse函数获取到证书信息
    • 当使用 dispatch_mode = 1/3 配置时,考虑到这种数据包分发策略用于无状态服务,当连接断开后相关信息会直接从内存中删除,所以Server->getClientInfo是获取不到相关连接信息的。

    • 返回 array 参数

      $fd_info = $server->getClientInfo($fd);
      var_dump($fd_info);
      array(7) {
        ["reactor_id"]=>
        int(3)
        ["server_fd"]=>
        int(14)
        ["server_port"]=>
        int(9501)
        ["remote_port"]=>
        int(19889)
        ["remote_ip"]=>
        string(9) "127.0.0.1"
        ["connect_time"]=>
        int(1390212495)
        ["last_time"]=>
        int(1390212760)
      }
参数作用
reactor_id来自哪个Reactor线程
server_fd来自哪个监听端口socket,这里不是客户端连接的fd
server_port来自哪个监听端口
remote_port客户端连接的端口
remote_ip客户端连接的IP地址
connect_time客户端连接到Server的时间,单位秒,由master进程设置
last_time最后一次收到数据的时间,单位秒,由master进程设置
close_errno连接关闭的错误码,如果连接异常关闭,close_errno的值是非零,可以参考Linux错误信息列表
websocket_status[可选项] WebSocket连接状态,当服务器是Swoole\WebSocket\Server时会额外增加此项信息
uid[可选项] 使用bind绑定了用户ID时会额外增加此项信息
ssl_client_cert[可选项] 使用SSL隧道加密,并且客户端设置了证书时会额外添加此项信息

getClientList()

遍历当前Server所有的客户端连接,Server::getClientList方法是基于共享内存的,不存在IOWait,遍历的速度很快。另外getClientList会返回所有TCP连接,而不仅仅是当前Worker进程的TCP连接。别名是Swoole\Server->connection_list()

Swoole\Server->getClientList(int $start_fd = 0, int $pageSize = 10): bool|array
  • 参数

    • int $start_fd

      • 功能:指定是起始fd
      • 默认值:无
      • 其它值:无
    • int $pageSize

      • 功能:每页取多少条,最大不得超过100
      • 默认值:无
      • 其它值:无
  • 返回值

    • 调用成功将返回一个数字索引数组,元素是取到的$fd。数组会按从小到大排序。最后一个$fd作为新的start_fd再次尝试获取
    • 调用失败返回false
  • 提示

    • 推荐使用 Server::$connections 迭代器来遍历连接
    • getClientList仅可用于TCP客户端,UDP服务器需要自行保存客户端信息
    • SWOOLE_BASE模式下只能获取当前进程的连接
  • 示例

  $start_fd = 0;
  while (true) {
      $conn_list = $server->getClientList($start_fd, 10);
      if ($conn_list === false or count($conn_list) === 0) {
          echo "finish\n";
          break;
      }
      $start_fd = end($conn_list);
      var_dump($conn_list);
      foreach ($conn_list as $fd) {
          $server->send($fd, "broadcast");
      }
  }

bind()

将连接绑定一个用户定义的UID,可以设置dispatch_mode=5设置以此值进行hash固定分配。可以保证某一个UID的连接全部会分配到同一个Worker进程。

Swoole\Server->bind(int $fd, int $uid)
  • 参数

    • int $fd

      • 功能:指定链接的 fd
      • 默认值:无
      • 其它值:无
    • int $uid

      • 功能:要绑定的UID,必须为非0的数字
      • 默认值:无
      • 其它值:无
  • 提示

    • 可以使用$serv->getClientInfo($fd) 查看连接所绑定UID的值
    • 在默认的dispatch_mode=2设置下,Server会按照socket fd来分配连接数据到不同的Worker进程。因为fd是不稳定的,一个客户端断开后重新连接,fd会发生改变。这样这个客户端的数据就会被分配到别的Worker。使用bind之后就可以按照用户定义的UID进行分配。即使断线重连,相同UIDTCP连接数据会被分配相同的Worker进程。
    • 时序问题
      • 客户端连接服务器后,连续发送多个包,可能会存在时序问题。在bind操作时,后续的包可能已经dispatch,这些数据包仍然会按照fd取模分配到当前进程。只有在bind之后新收到的数据包才会按照UID取模分配。
      • 因此如果要使用bind机制,网络通信协议需要设计握手步骤。客户端连接成功后,先发一个握手请求,之后客户端不要发任何包。在服务器bind完后,并回应之后。客户端再发送新的请求。
    • 重新绑定
      • 某些情况下,业务逻辑需要用户连接重新绑定UID。这时可以切断连接,重新建立TCP连接并握手,绑定到新的UID
  • 注意

!> -仅在设置dispatch_mode=5时有效
-未绑定UID时默认使用fd取模进行分配
-同一个连接只能被bind一次,如果已经绑定了UID,再次调用bind会返回false

  • 完整示例
$serv = new Swoole\Server("0.0.0.0", 9501);
$serv->fdlist = [];
$serv->set(array(
        'worker_num' => 4,
        'dispatch_mode' => 5,   //uid dispatch
));

$serv->on('connect', function ($serv, $fd, $reactor_id){
    //echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n";
    echo "{$fd} connect, worker:".$serv->worker_id.PHP_EOL;
});

$serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) {
    $conn = $serv->connection_info($fd);
    print_r($conn);
    echo "worker_id: " . $serv->worker_id . PHP_EOL;
    if (empty($conn['uid'])) {
        $uid = $fd + 1;
        if ($serv->bind($fd, $uid)) {
            $serv->send($fd, "bind {$uid} success");
        }
    } else {
        if (empty($serv->fdlist[$fd])) {
            $serv->fdlist[$fd] = $conn['uid'];
        }
        print_r($serv->fdlist);
        foreach ($serv->fdlist as $_fd => $uid) {
            $serv->send($_fd, "{$fd} say:" . $data . PHP_EOL);
        }
    }
});

$serv->on('close', function ($serv, $fd, $reactor_id) {
    //echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n";
    unset($serv->fdlist[$fd]);
});

$serv->start();

stats()

得到当前Server的活动TCP连接数,启动时间等信息,accpet/close(建立连接/关闭连接)的总次数等信息。

Swoole\Server->stats(): array
  • 参数

  • 返回值 array

  • 示例

        array(12) {
          ["start_time"]=>
          int(1580610688)
          ["connection_num"]=>
          int(1)
          ["accept_count"]=>
          int(1)
          ["close_count"]=>
          int(0)
          ["worker_num"]=>
          int(1)
          ["idle_worker_num"]=>
          int(1)
          ["tasking_num"]=>
          int(0)
          ["request_count"]=>
          int(1)
          ["worker_request_count"]=>
          int(1)
          ["worker_dispatch_count"]=>
          int(1)
          ["task_idle_worker_num"]=>
          int(1)
          ["coroutine_num"]=>
          int(1)
        }
参数作用
start_time服务器启动的时间
connection_num当前连接的数量
accept_count接受了多少个连接
close_count关闭的连接数量
worker_num开启了多少个worker进程
idle_worker_num空闲的worker进程数
tasking_num当前正在排队的任务数
request_countServer收到的请求次数 【只有onReceive、onMessage、onRequset、onPacket四种数据请求计算request_count】
worker_request_count当前Worker进程收到的请求次数【worker_request_count超过max_request时工作进程将退出】
worker_dispatch_countmaster进程向当前Worker进程投递任务的计数,在master进程进行dispatch时增加计数
task_queue_num消息队列中的task数量【用于Task】
task_queue_bytes消息队列的内存占用字节数【用于Task】
task_idle_worker_num空闲的task进程数量
coroutine_num当前协程数量coroutine_num【用于Coroutine】,想获取更多信息参考此节

task()

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

Swoole\Server->task(mixed $data, int $dstWorkerId  = -1): int
  • 参数

    • mixed $data

      • 功能:要投递的任务数据,必须是可序列化的PHP变量
      • 默认值:无
      • 其它值:无
    • int $dstWorkerId

      • 功能:可以指定要给投递给哪个 Task进程,传入ID即可,范围参考$worker_id
      • 默认值:-1
      • 其它值:无
  • 返回值

    • 调用成功,返回值为整数$task_id,表示此任务的ID。如果有finish回应,onFinish回调中会携带$task_id参数
    • 调用失败,返回值为false$task_id可能为0,因此必须使用===判断是否失败
  • 提示

    • 此功能用于将慢速的任务异步地去执行,比如一个聊天室服务器,可以用它来进行发送广播。当任务完成时,在task进程中调用$serv->finish("finish")告诉worker进程此任务已完成。当然Swoole\Server->finish是可选的。

    • task底层使用unixSocket通信,是全内存的,没有IO消耗。单进程读写性能可达100万/s,不同的进程使用不同的unixSocket通信,可以最大化利用多核。

    • 未指定目标Task进程,调用task方法会判断 Task进程的忙闲状态,底层只会向处于空闲状态的Task进程投递任务。如果所有Task进程均处于忙的状态,底层会轮询投递任务到各个进程。可以使用 server->stats 方法获取当前正在排队的任务数量。

    • 第三个参数,可以直接设置onFinish函数,如果任务设置了回调函数,Task返回结果时会直接执行指定的回调函数,不再执行ServeronFinish回调

      $server->task($data, -1, function (Swoole\Server $server, $task_id, $data) {
          echo "Task Callback: ";
          var_dump($task_id, $data);
      });
    • $task_id是从0-42亿的整数,在当前进程内是唯一的

    • 默认不启动task功能,需要在手动设置task_worker_num来启动此功能

    • TaskWorker的数量在Server::set()参数中调整,如task_worker_num => 64,表示启动64个进程来接收异步任务

    • 配置参数

      • Server->task/taskwait/finish 3个方法当传入的$data数据超过8K时会启用临时文件来保存。当临时文件内容超过 server->package_max_length 时底层会抛出一个警告。此警告不影响数据的投递,过大的Task可能会存在性能问题。
      WARN: task package is too big.
    • 单向任务

      • MasterManagerUserProcess进程中投递的任务,是单向的。在TaskWorker进程中无法使用returnServer->finish()方法返回结果数据。
  • 注意

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

  • 示例
    $server = new Swoole\Server("127.0.0.1", 9501, SWOOLE_BASE);

    $server->set(array(
        'worker_num'      => 2,
        'task_worker_num' => 4,
    ));

    $server->on('Receive', function (Swoole\Server $server, $fd, $from_id, $data) {
        echo "接收数据" . $data . "\n";
        $data    = trim($data);
        $server->task($data, -1, function (Swoole\Server $server, $task_id, $data) {
            echo "Task Callback: ";
            var_dump($task_id, $data);
        });
        $task_id = $server->task($data, 0);
        $server->send($fd, "分发任务,任务id为$task_id\n");
    });

    $server->on('Task', function (Swoole\Server $server, $task_id, $from_id, $data) {
        echo "Tasker进程接收到数据";
        echo "#{$server->worker_id}\tonTask: [PID={$server->worker_pid}]: task_id=$task_id, data_len=" . strlen($data) . "." . PHP_EOL;
        $server->finish($data);
    });

    $server->on('Finish', function (Swoole\Server $server, $task_id, $data) {
        echo "Task#$task_id finished, data_len=" . strlen($data) . PHP_EOL;
    });

    $server->on('workerStart', function ($server, $worker_id) {
        global $argv;
        if ($worker_id >= $server->setting['worker_num']) {
            swoole_set_process_name("php {$argv[0]}: task_worker");
        } else {
            swoole_set_process_name("php {$argv[0]}: worker");
        }
    });

    $server->start();

taskwait()

taskwaittask方法作用相同,用于投递一个异步的任务到 task进程池去执行。与task不同的是taskwait是同步等待的,直到任务完成或者超时返回。$result为任务执行的结果,由$server->finish函数发出。如果此任务超时,这里会返回false

Swoole\Server->taskwait(mixed $data, float $timeout = 0.5, int $dstWorkerId = -1): string|bool
  • 参数

    • int $data

      • 功能:投递的任务数据,可以是任意类型,非字符串类型底层会自动进行串化
      • 默认值:无
      • 其它值:无
    • float $timeout

      • 功能:超时时间,浮点型,单位为秒,最小支持1ms粒度,超过规定时间内 Task进程未返回数据,taskwait将返回false,不再处理后续的任务结果数据
      • 默认值:无
      • 其它值:无
    • int $dstWorkerId

      • 功能:指定要给投递给哪个 Task进程,传入ID即可,范围参考$worker_id
      • 默认值:-1【默认为-1表示随机投递,底层会自动选择一个空闲 Task进程
      • 其它值:无
  • 提示

    • 协程模式

      • 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): bool|array
  • 参数

    • int $tasks

      • 功能:必须为数字索引数组,不支持关联索引数组,底层会遍历$tasks将任务逐个投递到 Task进程
      • 默认值:无
      • 其它值:无
    • float $timeout

      • 功能:为浮点型,单位为秒
      • 默认值:0.5秒
      • 其它值:无
  • 返回值

    • 任务完成或超时,返回结果数组。结果数组中每个任务结果的顺序与$tasks对应,如:$tasks[2]对应的结果为$result[2]
    • 某个任务执行超时不会影响其他任务,返回的结果数据中将不包含超时的任务
  • 注意

!> -最大并发任务不得超过1024

  • 示例
  $tasks[] = mt_rand(1000, 9999); //任务1
  $tasks[] = mt_rand(1000, 9999); //任务2
  $tasks[] = mt_rand(1000, 9999); //任务3
  var_dump($tasks);

  //等待所有Task结果返回,超时为10s
  $results = $server->taskWaitMulti($tasks, 10.0);

  if (!isset($results[0])) {
      echo "任务1执行超时了\n";
  }
  if (isset($results[1])) {
      echo "任务2的执行结果为{$results[1]}\n";
  }
  if (isset($results[2])) {
      echo "任务3的执行结果为{$results[2]}\n";
  }

taskCo()

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

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

最大并发任务不得超过1024

  • 调度过程

  • $tasks列表中的每个任务会随机投递到一个Task工作进程,投递完毕后,yield让出当前协程,并设置一个$timeout秒的定时器

  • onFinish中收集对应的任务结果,保存到结果数组中。判断是否所有任务都返回了结果,如果为否,继续等待。如果为是,进行resume恢复对应协程的运行,并清除超时定时器

  • 在规定的时间内任务没有全部完成,定时器先触发,底层清除等待状态。将未完成的任务结果标记为false,立即resume对应协程

    • 示例
    <?php
    
    $server = new Swoole\Http\Server("127.0.0.1", 9502, SWOOLE_BASE);
    
    $server->set([
        'worker_num'      => 1,
        'task_worker_num' => 2,
    ]);
    
    $server->on('Task', function (Swoole\Server $serv, $task_id, $worker_id, $data) {
        echo "#{$serv->worker_id}\tonTask: worker_id={$worker_id}, task_id=$task_id\n";
        if ($serv->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();

finish()

用于在 Task进程中通知Worker进程,投递的任务已完成。此函数可以传递结果数据给Worker进程。

Swoole\Server->finish(string $data)
  • 参数

    • int param
      • 功能:无
      • 默认值:无
      • 其它值:无
  • 提示

    • finish方法可以连续多次调用,Worker进程会多次触发onFinish事件
    • onTask回调函数中调用过finish方法后,return数据依然会触发onFinish事件
    • Server->finish是可选的。如果Worker进程不关心任务执行的结果,不需要调用此函数
    • onTask回调函数中return字符串,等同于调用finish
  • 注意

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

heartbeat()

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

Swoole\Server->heartbeat(bool $ifCloseConnection = true): bool|array
  • 参数

    • bool $ifCloseConnection
      • 功能:是否关闭超时的连接
      • 默认值:true
      • 其它值:false
  • 返回值

    • 调用成功将返回一个连续数组,元素是已关闭的$fd
    • 调用失败返回false
  • 示例

    $closeFdArrary = $server->heartbeat();

getLastError()

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

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

getSocket()

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

Swoole\Server->getSocket()
  • 提示

    • 监听端口

      • 使用listen方法增加的端口,可以使用Swoole\Server\Port对象提供的getSocket方法。
      $port = $server->listen('127.0.0.1', 9502, SWOOLE_SOCK_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;
      }
    • 支持组播

      • 使用socket_set_option设置MCAST_JOIN_GROUP参数可以将Socket加入组播,监听网络组播数据包。
      <?php
      
      $server = new Swoole\Server('0.0.0.0', 9905, SWOOLE_BASE, SWOOLE_SOCK_UDP);
      $server->set(['worker_num' => 1]);
      $socket = $server->getSocket();
      
      $ret = socket_set_option(
          $socket,
          IPPROTO_IP,
          MCAST_JOIN_GROUP,
          array(
            'group' => '224.10.20.30', // 表示组播地址
            'interface' => 'eth0' // 表示网络接口的名称,可以为数字或字符串,如eth0、wlan0
          )
      );
      
      if ($ret === 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: $data");
          var_dump($addr, strlen($data));
      });
      
      $server->start();
  • 注意

!> 此方法需要依赖PHP的sockets扩展,并且编译Swoole时需要开启--enable-sockets选项

protect()

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

Swoole\Server->protect(int $fd, bool $value = true)
  • 参数

    • int $fd

      • 功能:指定客户端连接fd
      • 默认值:无
      • 其它值:无
    • int $value

      • 功能:设置的状态
      • 默认值:true 【表示保护状态】
      • 其它值:false 【表示不保护】

confirm()

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

Swoole\Server->confirm(int $fd);
  • 参数

    • int $fd
      • 功能:连接的唯一标识符
      • 默认值:无
      • 其它值:无
  • 返回值

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

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

  • 完整示例

//创建Server对象,监听 127.0.0.1:9501端口
$serv = new Swoole\Server("127.0.0.1", 9501); 
$serv->set([
    'enable_delay_receive' => true,
]);

//监听连接进入事件
$serv->on('Connect', function ($serv, $fd) {  
    //在这里检测这个$fd,没问题再confirm
    $serv->confirm($fd);
});

//监听数据接收事件
$serv->on('Receive', function ($serv, $fd, $from_id, $data) {
    $serv->send($fd, "Server: ".$data);
});

//监听连接关闭事件
$serv->on('Close', function ($serv, $fd) {
    echo "Client: Close.\n";
});

//启动服务器
$serv->start();