方法
__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
- 功能:指定运行模式
- 默认值:SWOOLE_PROCESS 多进程模式(默认)
- 其它值:SWOOLE_BASE 基本模式
int $sockType
- 功能:指定这组Server的类型
- 默认值:无
- 其它值:
SWOOLE_TCP/SWOOLE_SOCK_TCP
tcp ipv4 socketSWOOLE_TCP6/SWOOLE_SOCK_TCP6
tcp ipv6 socketSWOOLE_UDP/SWOOLE_SOCK_UDP
udp ipv4 socketSWOOLE_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_file 和 ssl_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
方法时会覆盖上一次的设定
参数
示例
$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
权限
主服务器是WebSocket
或Http
协议,新监听的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
对象 - 默认值:无
- 其它值:无
- 功能:
- Swoole\Process
注意
!> -创建的子进程可以调用
$server
对象提供的各个方法,如getClientList/getClientInfo/stats
-在Worker/Task
进程中可以调用$process
提供的方法与子进程进行通信
-在用户自定义进程中可以调用$server->sendMessage
与Worker/Task
进程通信
-用户进程内不能使用Server->task/taskwait
接口
-用户进程内可以使用Server->send/close
等接口
-用户进程内应当进行while(true)
(如下边的示例)或EventLoop循环(例如创建个定时器),否则用户进程会不停地退出重启提示
示例
$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();
start()
启动服务器,监听所有TCP/UDP
端口。
Swoole\Server->start(): bool
- 参数 无
!> 提示:以下以 SWOOLE_PROCESS 模式为例
提示
扩展
Master 主进程
- 主进程内有多个Reactor线程,基于
epoll/kqueue
进行网络事件轮询。收到数据后转发到Worker
进程去处理
- 主进程内有多个Reactor线程,基于
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
发送特定的信号,Server
的Worker
进程可以安全的结束。参考 如何正确的重启服务
参数
bool $only_reload_taskworkrer
- 功能:是否仅重启 Task进程
- 默认值:false
- 其它值:无
!> -reload
有保护机制,当一次reload
正在进行时,收到新的重启信号会丢弃
-如果设置了user/group
,Worker
进程可能没有权限向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
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/OpCache
,reload
重载入时会受到影响,有2
种解决方案- 打开
APC/OpCache
的stat
检测,如果发现文件更新APC/OpCache
会自动更新OpCode
- 在
onWorkerStart
中加载文件(require、include等函数)之前执行apc_clear_cache
或opcache_reset
刷新OpCode
缓存
- 打开
注意
!> -平滑重启只对onWorkerStart
或onReceive等在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
函数不接受任何参数 - 默认值:无
- 其它值:无
- 功能:回调函数,必须是可以调用的,
注意
!> -定时器的生命周期是进程级的,当使用reload
或kill
重启关闭进程时,定时器会全部被销毁
-如果有某些定时器存在关键逻辑和数据,请在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\Server
的fd
在上层回调方法的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
遇到缓存区已满时会自动挂起,当数据被对端读走一部分后恢复协程,继续发送数据。
- 在协程模式开启了send_yield情况下
- 监听UnixSocket DGRAM端口时,可以使用
send
向对端发送数据。
$server->on("packet", function (Swoole\Server $server, $data, $addr){ $server->send($addr['address'], 'SUCCESS', $addr['server_socket']); });
- 监听UnixSocket DGRAM端口时,可以使用
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
- 默认值:无
- 其它值:无
?>
$ip
为IPv4
或IPv6
字符串,如192.168.1.102
。如果IP
不合法会返回错误- 功能:指定客户端
int $port
- 功能:指定客户端
port
- 默认值:无
- 其它值:无
?>
$port
为1-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 - 默认值:无
- 其它值:无
- 功能:目标进程的
提示
- 在
Worker
进程内调用sendMessage
是异步IO的,消息会先存到缓冲区,可写时向unixSocket发送此消息 - 在 Task进程 内调用
sendMessage
默认是同步IO,但有些情况会自动转换成异步IO,参考同步IO转换成异步IO - 在 User进程 内调用
sendMessage
和Task一样,默认同步阻塞的,参考同步IO转换成异步IO
- 在
注意
!> - 如果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
- 功能:指定文件描述符
- 默认值:无
- 其它值:无
提示
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
进行分配。即使断线重连,相同UID
的TCP
连接数据会被分配相同的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_count | Server收到的请求次数 【只有onReceive、onMessage、onRequset、onPacket四种数据请求计算request_count】 |
worker_request_count | 当前Worker进程收到的请求次数【worker_request_count超过max_request时工作进程将退出】 |
worker_dispatch_count | master进程向当前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
,并且必须设置Server
的onTask和onFinish事件回调函数。
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
返回结果时会直接执行指定的回调函数,不再执行Server
的onFinish回调$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.
单向任务
- 从
Master
、Manager
、UserProcess
进程中投递的任务,是单向的。在TaskWorker
进程中无法使用return
或Server->finish()
方法返回结果数据。
- 从
注意
!> -task
方法不能在task进程/用户自定义进程中调用
-使用task
必须为Server
设置onTask和onFinish回调,否则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()
taskwait
与task
方法作用相同,用于投递一个异步的任务到 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进程】 - 其它值:无
- 功能:指定要给投递给哪个 Task进程,传入
提示
注意
!> -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
- 功能:无
- 默认值:无
- 其它值:无
提示
注意
!> 使用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 | 正在执行close ,onClose回调函数中不得使用$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();