进程作为程序执行过程中资源分配的基本单位,拥有独立的地址空间,同一进程的线程可以共享本进程的全局变量,静态变量等数据和地址空间,但进程之间资源相互独立.由于PHP语言不支持多线程,因此Swoole使用多进程模式,再多进程模式下就存在进程内存隔离,进程间通信与数据共享问题.
swoole中master主进程会创建manager管理进程和reactor线程,真正的工作进程为worker进程. manager是创建和管理worker进程,reactor进程测试监听socket,接受数据任务,发送给worker进程去工作,因此所有业务逻辑最终都是在worker进程中进行的,worker进程之间的数据共享与通信必不可少.
swoole中 设置选项worker_num设置 启动的worker进程数,默认设置为CPU核数
$server = new swoole_server('127.0.0.1',9898);
$server->set(array(
'worker_num' => 4, //设置启动的Worker进程数。
));
如上面说描述,进程存在进程隔离:
$fds = array();
$server->on('connect', function ($server, $fd){
echo "connection open: {$fd}\n";
global $fds;
$fds[] = $fd;
var_dump($fds);
});
$fds虽然是全局变量,但是只在但前的进程内有效,swoole服务器底层会创建多个worker进程,此处打印出来的只有部分连接的fd,本文讲简述两种解决方案的简单示例:
1.外部存储服务 : Redis
作为内存数据库redis 无太多IO等待,并且读写速度快
示例代码:以简易聊天室websocket服务 swoole_websocket_server为例
$ws = new swoole_websocket_server("0.0.0.0", 9999);
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$ws->set(array(
'daemonize' => true,
'worker_num' => 1,
));
//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) use($redis) {
var_dump($request->fd, $request->get, $request->server);
//记录连接
$redis->sAdd('fd',$request->fd);
$count = $redis->sCard('fd');
$ws->push($request->fd, 'hello, welcome ☺ 当前'.$count.'人连接在线');
});
//监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) use($redis) {
$fds = $redis->sMembers('fd');
$data = json_decode($frame->data,true);
if($data['type'] ==1 ){
$redis->setex($frame->fd,'7200',json_encode(['fd'=>$frame->fd,'user'=>$data['user']]));
//通知所有用户新用户上线
$fds = $redis->sMembers('fd');$users=[];
$i=0;
foreach ($fds as $fd_on){
$info = $redis->get($fd_on);
$is_time = $redis->ttl($fd_on);
if($is_time > 0){
$users[$i]['fd'] = $fd_on;
$users[$i]['name'] = json_decode($info,true)['user'];
// $users[$i]['name'] = $is_time;
}else{
$redis->sRem('fd',$fd_on);
}
$i++;
}
foreach ($fds as $fd_on){
$message = date('Y-m-d H:i:s',time())."
欢迎 ".$data['user']." 进入聊天室
";
$push_data = ['message'=>$message,'users'=>$users];
$ws->push($fd_on,json_encode($push_data));
$i++;
}
}else if($data['type'] ==2){
if($data['to_user'] == 'all'){
foreach ($fds as $fd){
if($frame->fd == $fd){
$message = "".date('Y-m-d H:i:s',time())."
寡人say: ".$data['msg']."
";
}else{
$message = date('Y-m-d H:i:s',time())."
".$data['from_user']." say: ".$data['msg']."
";
}
$push_data = ['message'=>$message];
$ws->push($fd,json_encode($push_data));
}
}
}
echo "Message: {$frame->data}\n";
//循环所有连接人发送内容
//foreach($ws->connections as $key => $fd) {
//$user_message = $frame->data;
//$ws->push($fd, $frame->fd.'say:'.$user_message);
//}
});
//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) use ($redis){
$redis->sRem('fd',$fd);
$fds = $redis->sMembers('fd');
$i=0;$users=[];
foreach ($fds as $fd_on){
$info = $redis->get($fd_on);
$is_time = $redis->ttl($fd_on);
if($is_time){
$users[$i]['fd'] = $fd_on;
$users[$i]['name'] = json_decode($info,true)['user'];
}else{
$redis->sRem('fd',$fd_on);
}
$i++;
}
foreach ($fds as $fd_on){
$user = json_decode($redis->get($fd),true)['user'];
$message = date('Y-m-d H:i:s',time())."
".$user." 离开聊天室了
";
$push_data = ['message'=>$message,'users'=>$users];
$ws->push($fd_on,json_encode($push_data));
}
echo "client-{$fd} is closed\n";
});
$ws->start();
2.共享内存拓展:swoole_table
swoole_table是swoole官方提供的基于共享内存和锁实现的超高性能冰饭数据结构.swoole_table在swoole1.7.5版本后可用.
目前swoole只支持3种类型:
swoole_table::TYPE_INT 整形字段
swoole_table::TYPE_FLOAT浮点字段
swoole_table::TYPE_STRING 字符串字段
函数方法:
column() :给内存表增加一列 参数:字段名,字段类型,字节数
$table->column('id', swoole_table::TYPE_INT, 4);
create():基于前一步对表结构的创建,执行创建表.
set() :设置行的数据(key-value的方式) 参数: 数据的key,数据的值(必须数组,键名必须与字段定义的$name相同)
$table->set($fd, ['id'=>1]);
get() :获取一行数据 参数:数据的key
$table->get($fd);
del() :删除一行数据 参数:数据的key
$table->del($fd);
lock():锁定整个表
unlock():释放锁
lock/unlock 必须成对出现,否则会发生死锁.
示例代码: 还是上面的websocket服务为例
class WebSocketServer {
private $server;
public function __construct()
{
$this->server = new swoole_websocket_server("0.0.0.0",9988);
$this->server->set(array(
'daemonize' => true,
'worker_num' => 4,
'task_worker_num' => 4
));
$fd_table = new swoole_table( 1024 );
$fd_table->column( "user",swoole_table::TYPE_STRING, 30 );
$fd_table->column( "time", swoole_table::TYPE_STRING, 20 );
$fd_table->create();
$user_table = new swoole_table(1024);
$user_table->column("fd",swoole_table::TYPE_INT,8);
$user_table->create();
$this->server->fd = $fd_table;
$this->server->user = $user_table;
//启动开始
$this->server->on('Start',[$this,'onStart']);
//与onStart同级
$this->server->on('workerStart',[$this,'onWorkerStart']);
//webSocket open 连接触发回调
$this->server->on('open',[$this,'onOpen']);
//webSocket send 发送触发回调
$this->server->on('message', [$this, 'onMessage']);
//webSocket close 关闭触发回调
$this->server->on('Close', [$this, 'onClose']);
//tcp连接 触发 在 webSocket open 之前回调
$this->server->on('Connect', [$this, 'onConnect']);
//tcp 模式下(eg:telnet ) 发送信息才会触发 webSocket 模式下没有触发
$this->server->on('Receive', [$this, 'onReceive']);
// task_worker进程处理任务的回调 处理比较耗时的任务
$this->server->on('Task', [$this, 'onTask']);
// task_worker进程处理任务结束的回调
$this->server->on('Finish', [$this, 'onFinish']);
// 服务开启
$this->server->start();
}
public function createTable(){
}
public function onStart( $server)
{
echo "Start\n";
}
public function onWorkerStart($server,$worker_id)
{
//判断是worker进程还是 task_worker进程 echo 次数 是worker_num+task_worker_num
if($worker_id<$server->setting['worker_num']){
echo 'worder'.$worker_id."\n";
}else{
echo 'task_worker'.$worker_id."\n";
}
// echo "workerStart{$worker_id}\n";
}
public function onOpen( $server,$request)
{
$this->server->fd->set($request->fd,['user'=>'']);
echo "server: handshake success with fd{$request->fd}\n";
$count =count($server->connections);
$server->push($request->fd, 'hello, welcome ☺ 当前'.$count.'人连接在线');
}
public function onMessage( $server,$frame)
{
echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
$data = json_decode($frame->data,true);
if($data['type'] ==1 ){
$server->fd->set($frame->fd,['user'=>$data['user']]);
//通知所有用户新用户上线
foreach($server->connections as $key => $fd) {
$server->push($fd, "欢迎 ".$data['user']." 进入聊天室");
}
}else if($data['type'] ==2){
if($data['to_user'] == 'all'){
foreach($server->connections as $key => $fd) {
$server->push($fd, "".$data['from_user']." say: ".$data['msg']);
}
}
}
}
public function onConnect( $server, $fd, $from_id ) {
echo "Client {$fd} connect\n";
echo "{$from_id}\n";
}
public function onReceive( $server, $fd, $from_id, $data ) {
echo "Get Message From Client {$fd}:{$data}\n";
// send a task to task worker.
// $param = array(
// 'fd' => $fd
// );
// $server->task( json_encode( $param ) );
echo "Continue Handle Worker\n";
}
public function onClose($server, $fd)
{
echo "Client {$fd} close connection\n";
foreach($server->connections as $key => $on_fd) {
$user = $server->fd->get($fd)['user'];
$server->push($on_fd, "".$user." 离开聊天室了");
}
}
public function onTask($server, $task_id, $from_id, $data)
{
echo "This Task {$task_id} from Worker {$from_id}\n";
echo "Data: {$data}\n";
for ($i = 0; $i < 10; $i++) {
sleep(1);
echo "Taks {$task_id} Handle {$i} times...\n";
}
$fd = json_decode($data, true)['fd'];
echo "Data in Task {$task_id}";
// $serv->send($fd, "Data in Task {$task_id}");
return "Task {$task_id}'s result";
}
public function onFinish($server,$task_id, $data) {
echo "Task {$task_id} finish\n";
echo "Result: {$data}\n";
}
}
new WebSocketServer();