手记

二、Swoole学习之玩转网络通信引擎(非常重要)

2.1 TCP服务&TCP客户端

2.1.1 TCP服务

<?php
//创建Server对象,监听 127.0.0.1:9501端口
$serv = new swoole_server("127.0.0.1", 9501);
//swoole_server->set函数用于设置swoole_server运行时的各项参数
$serv->set([
    'worker_num' => 6 , // worker进程数,cpu 1-4倍
    'max_request' => 10000,
]);
/**
 * 监听连接进入事件
 * $fd 客户端连接的唯一标示
 * $reactor_id 线程id
 */
$serv->on('connect', function ($serv, $fd, $reactor_id) {
    echo "Client: {$reactor_id} - {$fd}-Connect.\n";
});
/**
 * 监听数据接收事件
 * $reactor_id = $from_id
 */
$serv->on('receive', function ($serv, $fd, $reactor_id, $data) {
    echo "client:".$data;
    $serv->send($fd, "Server: {$reactor_id} - {$fd}".$data);
});
//监听连接关闭事件
$serv->on('close', function ($serv, $fd) {
    echo "Client: Close.\n";
});

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

<b>测试tcp服务器方法:</b>

netstat -anp | grep 9501

通过telnet方式登录远程主机:telnet 127.0.0.1 9501

<b>tcp客户端脚本 查看当前worker进程数:</b>

ps -aft | grep tcp_server.php

2.1.2 TCP客户端

<?php
/**
 * Created by PhpStorm.
 * User: yefy
 * Date: 2018/6/22
 * Time: 17:34
 */
$client = new swoole_client(SWOOLE_SOCK_TCP);

if (!$client->connect('127.0.0.1', 9501))
{
    exit("connect failed. Error: {$client->errCode}\n");
}
//php cli命令行模式 STDOUT常量 向屏幕输出消息
fwrite(STDOUT,"请输入消息:");

$msg = fgets(STDIN);

$client->send($msg);

echo $client->recv();

$client->close();

<b>Tips<b> :为了保证程序执行的完整性,当修改tcp服务器脚本后最好设置平滑重启worker进程
平滑重启worker进程

2.2 HTTP_Server和nginx类似
<?php
/**
 * Created by PhpStorm.
 * User: yefy
 * Date: 2018/6/23
 * Time: 10:53
 */

$http_server =  new swoole_http_server("0.0.0.0",9503);
/**
 * https://wiki.swoole.com/wiki/page/783.html
 * 静态资源处理
 * 配置静态文件根目录,与enable_static_handler配合使用。
 * 设置document_root并设置enable_static_handler为true后,
 * 底层收到Http请求会先判断document_root路径下是否存在此文件,
 * 如果存在会直接发送文件内容给客户端,不再触发onRequest回调。
 */
$http_server->set(
    [
        'enable_static_handler' => true,
        'document_root' => "/var/www/html/swoole_imooc/demo/data",
    ]
);
$http_server->on('request',function ($request,$response){
    $data = array(
            'date:' => date("Ymd H:i:s"),
            'get:' => $request->get,
            'post:' => $request->post,
            'header:' => $request->header,
    );
    /**
     * 异步写文件,调用此函数后会立即返回。当写入完成时会自动回调指定的callback函数
     */
    swoole_async_writefile('./access.log', json_encode($data).PHP_EOL,function ($filename){},FILE_APPEND);
    $response->cookie('yfyjsz','imooc',time()+1800);
    $response->end("sss".json_encode($request->get));
});
$http_server->start();

<b>Tips</b>:阿里云或者腾讯云都需要在安全组设置端口可以访问

2.3 swoole_websocket服务

<hr/>

2.3.1 基本概述

什么websocket

websocket协议是基本web的一种新的网络协议,实现了浏览器和服务器的全双工(==full deple==)允许服务器主动发送消息给客户端

为什么要使用websocket

  • 因为http无法主动发送消息给客户端

websocket的特点

  • 建立在tcp协议之上
  • 性能开销小,通信高效
  • 客户端可以和任意服务器通信
  • 协议标识符ws wss
  • 持久化网络通信标识

2.3.2 案列实现

2.3.2.1 服务端实现

1.面向过程实现 ws_server.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/6/28
 * Time: 17:19
 */
$server = new swoole_websocket_server("0.0.0.0", 9503);

$server->set(
    [
        'enable_static_handler' => true,
        'document_root' => "/var/www/html/swoole_imooc/demo/data",
    ]
);
$server->on('open', 'onOpen');

//客户端链接时
function onOpen($server,$request){
    echo "server: handshake success with fd{$request->fd}\n";
}

//ws收到信息时候
$server->on('message', function (swoole_websocket_server $server, $frame) {
    echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
    $server->push($frame->fd, "this is server");
});

//客户端关闭连接时
$server->on('close', function ($ser, $fd) {
    //
    echo "client {$fd} closed\n";
});

$server->start();

2.面向对象实现ws.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/6/28
 * Time: 18:37
 */
class Ws {
    const PORT =9503;
    const HOST = '0.0.0.0';
    public $ws = null;
    public function __construct()
    {
        $this->ws = new swoole_websocket_server(self::HOST,self::PORT);
        //php回调函数写法,参考文档https://wiki.swoole.com/wiki/page/458.html
        //注意这个$this是$this->ws对象
        $this->ws->on('open',array($this,'onOpen'));
        $this->ws->on('message',array($this,'onMessage'));
        $this->ws->on('close',array($this,'onClose'));
        $this->ws->start();
    }

    /**
     * @param $ws
     * @param $request
     * 监听客户端链接事件
     */
    public function onOpen($ws,$request){
        print_r('客户端链接:'.$request->fd);
    }

    /**
     * @param $ws
     * @param $request
     * 监听收到信息事件
     */
    public function onMessage($ws,$request){
        print_r($ws);
        echo "receive from {$request->fd}:{$request->data},opcode:{$request->opcode},fin:{$request->finish}\n";
        $ws->push($request->fd, "this is server");
    }

    /**
     * @param $ws
     * @param $request
     * 监听到客户端关闭的事件
     */
    public function onClose($ws,$fd){
        echo "client close:".$fd;
    }
}

new Ws();

2.3.2.2 客户端实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ws_测试</title>
</head>
<body>
    <h1>yfyjsz_swoole_ws_测试</h1>
    <script>
        var wsUrl = "ws://193.112.10.62:9503";
        var ws_server = new WebSocket(wsUrl);

        //实例化对象的onOpen属性
        ws_server.onopen = function(evt){
            console.log("connect_success");
            ws_server.send("I am a client");
        };
        //实例化对象的onMessage属性
        ws_server.onmessage = function (evt) {
            console.log('data:' + evt.data);
        };
        //实例化对象的onClose属性
        ws_server.onclose = function(evt){
            console.log('close client');
        };
        //实例化对象的onError属性
        ws_server. = function(evt,e){
            console.log('error:' + evt.data);
        }

    </script>
</body>
</html>

2.3.2.3 测试

  1. 利用静态文件目录进行测试
  2. 使用https_server进行访问测试
2.4 异步任务task(==重点==)

使用场景

  • 广播,发送邮件机制

Tips

  • 投递异步任务后会继续往下面执行,不会影响其他的任务
  • 当worker进程投递的任务在task_worker中完成时,task进程会通过swoole_server->finish()方法将任务处理的结果发送给worker进程。
  • task进程的onTask事件中没有调用finish方法或者return结果,worker进程不会触发onFinish

实现代码:

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/6/28
 * Time: 18:37
 */
class Ws {
    const PORT =9503;
    const HOST = '0.0.0.0';
    public $ws = null;
    public function __construct()
    {
        $this->ws = new swoole_websocket_server(self::HOST,self::PORT);
        //php回调函数写法,参考文档https://wiki.swoole.com/wiki/page/458.html
        //注意这个$this是$this->ws对象
        $this->ws->on('open',array($this,'onOpen'));
        $this->ws->on('message',array($this,'onMessage'));
        $this->ws->set([
            'worker_num'=>2,
            'task_worker_num'=>2
        ]);
        $this->ws->on("task", [$this, 'onTask']);
        $this->ws->on("finish", [$this, 'onFinish']);
        $this->ws->on('close',array($this,'onClose'));
        $this->ws->start();
    }

    /**
     * @param $ws
     * @param $request
     * 监听客户端链接事件
     */
    public function onOpen($ws,$request){
        print_r('客户端链接:'.$request->fd);
    }

    /**
     * @param $ws
     * @param $request
     * 监听收到信息事件
     */
    public function onMessage($ws,$request){
       //TODO 10s
        $data = array(
            'task'=>1,
            'fd'=>$request->fd
        );
        //广播,发送邮件比较耗时的任务
        $ws->task($data);
        echo "receive from {$request->fd}:{$request->data},opcode:{$request->opcode},fin:{$request->finish}\n";
        $ws->push($request->fd, "this is server");
    }

    public function onTask($serv,$task_id,$src_worker_id,  $data){
        sleep(10);
        print_r($data);
        return " i am a task";
    }

    public function onFinish( $serv,  $task_id,  $data){
        echo "taskId:{$task_id}\n";
        echo $data;

    }

    /**
     * @param $ws
     * @param $request
     * 监听到客户端关闭的事件
     */
    public function onClose($ws,$fd){
        echo "client close:".$fd;
    }
}

new Ws();
三、异步非堵塞IO场景

3.1 swoole毫秒定时器

异步高精度定时器,粒度为毫秒级

//定时执行
swoole_timer_tick(2000, function ($timer_id){
                echo "我是异步定时器函数swoole_timer_tick,我的timer_id:{$timer_id}\n";
            });

//执行一次
 swoole_timer_after(5000,  function ()use($ws,$request){
            echo "我是5s之后在执行的";
            $ws->push($request->fd,'我是5s之后在执行的函数');
        });

3.2 异步文件系统IO

3.2.1 异步文件系统IO-读取文件

swoole_async_readfile(__DIR__."/1.txt", function($filename, $content) {
    echo "filename is {$filename}".PHP_EOL;
    echo "content is {$content}".PHP_EOL;
});

3.2.2 异步文件系统-写入文件

$http->on('request', function($request, $response) {
    $content = [
        'date:' => date("Ymd H:i:s"),
        'get:' => $request->get,
        'post:' => $request->post,
        'header:' => $request->header,
    ];
    swoole_async_writefile(__DIR__."/access.log", json_encode($content).PHP_EOL, function($filename){
        // todo
    }, FILE_APPEND);
    $response->end("response:". json_encode($request->get));
});
[参考文档](https://wiki.swoole.com/wiki/page/185.html)

3.3异步mysql详解

<?php
/**
 * Created by PhpStorm.
 * User: yefy
 * Date: 2018/6/29
 * Time: 15:04
 */
class AsyncMysql{
    /**
     * @var array
     * 数据库的配置
     */
    private $dbConfig = [];

    public $dbSource = '';

    /**
     * AsyncMysql constructor.
     * 构造函数
     */
    public function __construct()
    {
        $this->dbConfig = array(
            'host' => '127.0.0.1',
            'port' => 3306,
            'user' => 'root',
            'password' => '8912878yfy',
            'database' => 'test',
            'charset' => 'utf8',
        );
        $this->dbSource = new Swoole\Mysql;
    }

    /**
     * @param $id
     * @param $username
     * 执行主函数
     */
    public function execute($id,$username){
        $this->dbSource->connect($this->dbConfig,function ($db,$result)use($id,$username){
            if($result === false) {
                var_dump($db->connect_error);
                // todo
                die;
            }
            $sql = "insert into user VALUE ('2','yfy')";
            //$sql = "show tables";
            $db->query($sql,function ($db,$result){
                if($result === false){
                    var_dump($db->error);
                }elseif($result === true){ //add update
                    var_dump($db->affected_rows);
                }else{
                    print_r($result);
                }
                $db->close();
            });
            return true;
        });
    }
}
$obj = new AsyncMysql();
$flag = $obj->execute(1,'yfyjsz');
//先执行后执行sql语句,因为是异步过程
echo 'start';
var_dump($flag);

3.4异步redis详解

3.4.1 环境准备

swoole使用redis的==前置条件==

  • redis服务
  • hiredis服务
  • 编译swoole时需要加--enable-aysnc-redis参数

编译安装hiredis

下载hiredis源码,然后执行

  • make -j
  • make install
  • ldconfig

hiredis下载地址

重新编译swoole

  • ./configure --with-php-config=/usr/local/php/bin/php-config --enable-async-redis
  • make clean
  • make -j & make install

查看php扩展php -m 出现==扩展无法加载==,解决方案如下:

解决方案:
用vi打开当前用户的bash_profile
vi ~/.bash_profile
在最后一行添加
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
最后再 source ~/.bash_profile

查看安装的扩展的详细信息命令

php --ri swoole

3.4.2 代码测试

<?php
/**
 * Created by PhpStorm.
 * User: yefy
 * Date: 2018/6/29
 * Time: 16:30
 */
$redis_client = new swoole_redis;
$redis_client->connect('127.0.0.1',6379,function (swoole_redis $redis_client,$result){
   var_dump($result.PHP_EOL);
   echo "client_connect ok";
   $redis_client->set('yfyjsz',time(),function (swoole_redis $redis_client,$result){
       if($result == 'OK'){
           echo "yfyjsz设置成功";
       }else{
           echo "yfyjsz设置失败";
       }
   });
    $redis_client->set('yfyjsz1',time(),function (swoole_redis $redis_client,$result){
        if($result == 'OK'){
            echo "yfyjsz设置成功";
        }else{
            echo "yfyjsz设置失败";
        }
    });
    $redis_client->get('yfyjsz',function (swoole_redis $redis_client,$result){
        var_dump($result);
    });
    //模糊匹配
    $redis_client->keys('*fy*',function (swoole_redis $redis_client,$result){
        var_dump($result);
    });
   echo "start\n";
});
0人推荐
随时随地看视频
慕课网APP