存档

‘Swoole扩展’ 分类的存档

PHP开发异步高性能的MySQL代理服务器

2014年3月6日 2 条评论

MySQL数据库对每个客户端连接都会分配一个线程,所以连接非常宝贵。开发一个异步的MySQL代理服务器,PHP应用服务器可以长连接到这台Server,既减轻MYSQL的连接压力,又使PHP保持长连接减少connect/close的网络开销。

此Server考虑到了设置了数据库连接池尺寸,区分忙闲,mysqli断线重连,并设置了负载保护。基于swoole扩展开发,io循环使用epoll,是全异步非阻塞的,可以应对大量TCP连接。

程序的逻辑是:启动时创建N个MySQL连接,收到客户端发来的SQL后,分配1个MySQL连接,将SQL发往数据库服务器。然后等待数据库返回查询结果。当数据库返回结果后,再发给对应的客户端连接。

核心的数据结构是3个PHP数组。idle_pool是空闲的数据库连接,当有SQL请求时从idle_pool中移到busy_pool中。当数据库返回结果后从busy_pool中再移到idle_pool中,以供新的请求使用。当SQL请求到达时如果没有空闲的数据库连接,那会自动加入到wait_queue中。一旦有SQL完成操作,将自动从wait_queue中取出等待的请求进行处理。

如此循环使用。由于整个服务器是异步的单进程单线程所以完全不需要锁。而且是完全异步的,效率非常高。

当然本文的代码,如果要用于生产环境,还需做更多的保护机制和压力测试。在此仅抛砖引玉,提供一个解决问题的思路。


class DBServer
{
    protected $pool_size = 20;
    protected $idle_pool = array(); //空闲连接
    protected $busy_pool = array(); //工作连接
    protected $wait_queue = array(); //等待的请求
    protected $wait_queue_max = 100; //等待队列的最大长度,超过后将拒绝新的请求

    /**
     * @var swoole_server
     */
    protected $serv;

    function run()
    {
        $serv = new swoole_server("127.0.0.1", 9509);
        $serv->set(array(
            'worker_num' => 1,
        ));

        $serv->on('WorkerStart', array($this, 'onStart'));
        //$serv->on('Connect', array($this, 'onConnect'));
        $serv->on('Receive', array($this, 'onReceive'));
        //$serv->on('Close', array($this, 'onClose'));
        $serv->start();
    }

    function onStart($serv)
    {
        $this->serv = $serv;
        for ($i = 0; $i < $this->pool_size; $i++) {
            $db = new mysqli;
            $db->connect('127.0.0.1', 'root', 'root', 'test');
            $db_sock = swoole_get_mysqli_sock($db);
            swoole_event_add($db_sock, array($this, 'onSQLReady'));
            $this->idle_pool[] = array(
                'mysqli' => $db,
                'db_sock' => $db_sock,
                'fd' => 0,
            );
        }
        echo "Server: start.Swoole version is [" . SWOOLE_VERSION . "]\n";
    }

    function onSQLReady($db_sock)
    {
        $db_res = $this->busy_pool[$db_sock];
        $mysqli = $db_res['mysqli'];
        $fd = $db_res['fd'];

        echo __METHOD__ . ": client_sock=$fd|db_sock=$db_sock\n";

        if ($result = $mysqli->reap_async_query()) {
            $ret = var_export($result->fetch_all(MYSQLI_ASSOC), true) . "\n";
            $this->serv->send($fd, $ret);
            if (is_object($result)) {
                mysqli_free_result($result);
            }
        } else {
            $this->serv->send($fd, sprintf("MySQLi Error: %s\n", mysqli_error($mysqli)));
        }
        //release mysqli object
        $this->idle_pool[] = $db_res;
        unset($this->busy_pool[$db_sock]);

        //这里可以取出一个等待请求
        if (count($this->wait_queue) > 0) {
            $idle_n = count($this->idle_pool);
            for ($i = 0; $i < $idle_n; $i++) {
                $req = array_shift($this->wait_queue);
                $this->doQuery($req['fd'], $req['sql']);
            }
        }
    }

    function onReceive($serv, $fd, $from_id, $data)
    {
        //没有空闲的数据库连接
        if (count($this->idle_pool) == 0) {
            //等待队列未满
            if (count($this->wait_queue) < $this->wait_queue_max) {
                $this->wait_queue[] = array(
                    'fd' => $fd,
                    'sql' => $data,
                );
            } else {
                $this->serv->send($fd, "request too many, Please try again later.");
            }
        } else {
            $this->doQuery($fd, $data);
        }
    }

    function doQuery($fd, $sql)
    {
        //从空闲池中移除
        $db = array_pop($this->idle_pool);
        /**
         * @var mysqli
         */
        $mysqli = $db['mysqli'];

        for ($i = 0; $i < 2; $i++) {
            $result = $mysqli->query($sql, MYSQLI_ASYNC);
            if ($result === false) {
                if ($mysqli->errno == 2013 or $mysqli->errno == 2006) {
                    $mysqli->close();
                    $r = $mysqli->connect();
                    if ($r === true) continue;
                }
            }
            break;
        }

        $db['fd'] = $fd;
        //加入工作池中
        $this->busy_pool[$db['db_sock']] = $db;
    }
}

$server = new DBServer();
$server->run();
分类: Linux, PHP, Swoole扩展 标签:

swoole的异步和同步

2014年2月1日 没有评论

有人问node.js和swoole有何不同,最大的不同当然是语言了,一个是Javascript一个是PHP。除此之外最大的不同是,swoole不仅支持异步,还支持同步。而node.js只支持异步,代码中不能有任何阻塞,否则程序就会变得效率很差。

swoole如果选择异步模式,所有网络连接应当使用swoole_client。其他数据库查询,API调用都应当使用swoole_event_add将socket加入到异步事件循环中。一些必须要阻塞的操作就用swoole提供的task/finish功能进行异步化。异步方式单个worker进程的程序效率很高,不存在IOWait,所以一个进程就能同时处理成千上万的请求,只需要开启CPU核数的1倍或2倍的worker进程即可。

如果选择阻塞模式,那在你的代码中就像php-fpm或mod_php中那样编码即可。另外根据系统的负载情况设定足够多的worker进程。比如每个请求需要50ms,那单个worker进程只能达到20QPS。启动100个worker进程就可以有2000QPS的处理能力。当然进程越多那浪费在进程间切换的CPU时间就会越多,系统的效率越来越差。

有人还提出假如Server的处理能力是100QPS,那同时并发10000个请求怎么办?这个问题就像银行窗口一样,假如他们的处理能力是每小时100个客户,假设来了1万个人,那只能排队等候处理或者丢弃一部分人。Server也是一样,超过处理能力的请求都会排队放在socket缓冲区中,如果socket缓冲区也满了,那新的请求就将丢弃。

分类: PHP, Swoole扩展 标签:

基于swoole扩展实现真正的PHP数据库连接池

2014年1月15日 2 条评论

PHP的数据库连接池一直以来都是一个难题,很多从PHP语言转向Java的项目,大多数原因都是因为Java有更好的连接池实现。PHP的MySQL扩展提供了长连接的API,但在PHP机器数量较多,规模较大的情况下,mysql_pconnect非但不能节约MySQL资源,反而会加剧数据库的负荷。

假设有100台PHP的应用服务器,每个机器需要启动100个apache或fpm工作进程,那每个进程都会产生一个长连接到MySQL。这一共会产生1万个My SQL连接。大家都知道MySQL是每个连接会占用1个线程。那MYSQL就需要创建1万个线程,这样大量的系统资源被浪费在线程间上下文切换上。而你的业务代码中并不是所有地方都在做数据库操作,所以这个就是浪费的。

连接池就不同了,100个worker进程,公用10个数据库连接即可,当操作完数据库后,立即释放资源给其他worker进程。这样就算有100台PHP的服务器,那也只会创建1000个MySQL的连接,完全可以接受的。

以前确实没有好的办法来解决此问题的,现在有了swoole扩展,利用swoole提供的task功能可以很方便做出一个连接池来。

代码如下:


$serv = new swoole_server("127.0.0.1", 9508);
$serv->set(array(
    'worker_num' => 100,
    'task_worker_num' => 10, //MySQL连接的数量
));

function my_onReceive($serv, $fd, $from_id, $data)
{
    //taskwait就是投递一条任务,这里直接传递SQL语句了
    //然后阻塞等待SQL完成
    $result = $serv->taskwait("show tables");
    if ($result !== false) {
        list($status, $db_res) = explode(':', $result, 2);
        if ($status == 'OK') {
            //数据库操作成功了,执行业务逻辑代码,这里就自动释放掉MySQL连接的占用
            $serv->send($fd, var_export(unserialize($db_res), true) . "\n");
        } else {
            $serv->send($fd, $db_res);
        }
        return;
    } else {
        $serv->send($fd, "Error. Task timeout\n");
    }
}

function my_onTask($serv, $task_id, $from_id, $sql)
{
    static $link = null;
    if ($link == null) {
        $link = mysqli_connect("127.0.0.1", "root", "root", "test");
        if (!$link) {
            $link = null;
            $serv->finish("ER:" . mysqli_error($link));
            return;
        }
    }
    $result = $link->query($sql);
    if (!$result) {
        $serv->finish("ER:" . mysqli_error($link));
        return;
    }
    $data = $result->fetch_all(MYSQLI_ASSOC);
    $serv->finish("OK:" . serialize($data));
}

function my_onFinish($serv, $data)
{
    echo "AsyncTask Finish:Connect.PID=" . posix_getpid() . PHP_EOL;
}

$serv->on('Receive', 'my_onReceive');
$serv->on('Task', 'my_onTask');
$serv->on('Finish', 'my_onFinish');
$serv->start();

这里task_worker_num就是要启用的数据库连接池数量,worker进程为100时,连接池数量为10就可以设置为worker_num = 100, task_worker_num = 10。

Yii/Yaf/Swoole3个框架的压测性能对比

2013年12月18日 1 条评论

本次压测使用的是一台4核I5 CPU,8G内存的笔记本。使用的版本是:

swoole_framework github最新版
Yaf 2.2.9
Yii-1.1.14

php-fpm和swoole应用服务器均启用了8个进程,因为仅比较hello world,不存在阻塞,8进程足以压满CPU。php5-fpm中已经启用了apc对所有PHP进行缓存。

压测使用ab工具,参数为:

 ab -c 100 -n 10000

Yaf

Server Software: nginx/1.2.6
 Server Hostname: localhost
 Server Port: 80
 Document Path: /yafapp/
 Document Length: 11 bytes
 Concurrency Level: 100
 Time taken for tests: 1.336 seconds
 Complete requests: 10000
 Failed requests: 0
 Write errors: 0
 Total transferred: 1770000 bytes
 HTML transferred: 110000 bytes
 Requests per second: 7486.03 [#/sec] (mean)
 Time per request: 13.358 [ms] (mean)
 Time per request: 0.134 [ms] (mean, across all concurrent requests)
 Transfer rate: 1293.97 [Kbytes/sec] received

Yii

Server Software: nginx/1.2.6
 Server Hostname: localhost
 Server Port: 80
 Document Path: /yiiapp/
 Document Length: 11 bytes
 Concurrency Level: 100
 Time taken for tests: 8.016 seconds
 Complete requests: 10000
 Failed requests: 0
 Write errors: 0
 Total transferred: 1770000 bytes
 HTML transferred: 110000 bytes
 Requests per second: 1247.55 [#/sec] (mean)

SwooleFramework

 Server Software: Swoole
 Server Hostname: 127.0.0.1
 Server Port: 8888
 Document Path: /hello/index
 Document Length: 11 bytes 
 Concurrency Level: 100
 Time taken for tests: 1.470 seconds
 Complete requests: 10000
 Failed requests: 0
 Write errors: 0
 Total transferred: 2750000 bytes
 HTML transferred: 110000 bytes
 Requests per second: 6801.78 [#/sec] (mean)
 Time per request: 14.702 [ms] (mean)
 Time per request: 0.147 [ms] (mean, across all concurrent requests)
 Transfer rate: 1826.65 [Kbytes/sec] receive

结果评价

有些人说框架的消耗和业务逻辑相比不算什么,这句话在你的QPS不到10个时是完全正确的。但如果你的网站访问量很大,机器负载很高,那么框架的性能消耗就会很明显的体现出来了。

Yii压测只有1200QPS,假设网站的平均QPS为200,因为框架的消耗是纯CPU消耗,也就是光Yii就会带来(200/1200)*100%=16.7%的CPU消耗。而Yaf/Swoole的QPS可达7000,200qps仅占用

(200/7000)*100%=2.8%的CPU消耗。当然200QPS对于一个巨型网站来说只是一个小数目,框架的CPU消耗会随着QPS增加而直线上升。

Swoole框架的早期版本可达9000QPS,swoole框架本身还是很复杂的,因为全部是PHP的代码实现,仅底层使用了swoole扩展,如果做了足够的精简和优化,还是有很大的上升空间。

压测证明Yaf的性能非常好。如果你希望使用PHP应用服务器可以选择Swoole,如果是LAMP/LNMP架构可以使用Yaf框架。

分类: PHP, Swoole扩展, Swoole框架 标签:

swoole的worker进程之间如何共享数据

2013年12月9日 2 条评论

swoole的TCP连接都是以数字的方式提供给PHP端的,在PHP代码中只需要保存fd/from_id这2个数字,即可向对应的连接发送数据。swoole本身也提供了可以遍历所有连接的函数接口(swoole_connection_list/swoole_connection_info)。这两个函数在EventWorker/TaskWorker均可调用。
但只能保存与网络服务相关的数据,如来源端口,客户端IP,客户端port等信息。swoole本身没有提供操作共享内存的接口,如果业务代码中希望保存额外的信息,或在worker进程之间共享数据,可以使用第三方的工具库。

单机环境单应用

可以使用apc扩展提供的apc_store/apc_fetch/apc_delete来保存数据,使用APCIterator来遍历数据,APCIterator可以使用正则来匹配key,功能还是很强大的。
apc是使用锁+共享内存实现的,性能很好。但只能用于单个Swoole的服务器实例,并且数据是非持久化的,Server结束时数据会被清空。如果想要保存APC中的数据可以使用 apc_bin_dumpfile 和 apc_bin_loadfile 。


if(pcntl_fork() > 0)
{
    apc_store("hello", "swoole");
    sleep(10000);
}
else
{
    usleep(1000);
    echo "Child:",apc_fetch("hello2");
}

读写内存文件

Linux/FreeBSD下都提供了tmpfs,基于内存的虚拟文件系统。Linux下是/dev/shm,读写这个目录下的文件没有磁盘IO,只是内存操作,性能非常高。Oracle数据库中就用到了它。

Swoole的应用中可以使用/dev/shm + swoole_lock来存储/共享运行时的数据。

单机跨应用

levelDB/rocksdb或者读写文件。文件读写时应当加锁,可以使用swoole提供的swoole_lock类。当然也可以直接使用分布式的存储,如memcache/mysql/redis等。

分布式环境

使用memcache/mysql/redis/ttserver等存储服务。

 

分类: Swoole扩展 标签:

为什么选择swoole?

2013年11月25日 没有评论

Golang

golang很不错,性能好,开发效率高。协程+通道既解决了异步IO,又解决了并行问题。但Go目前存在gc和协程调度的问题,Java之类语言花了近20年的时间来完善GC,GO估计3-5年之内很难解决。同样node.js也存在此问题。

gc问题:golang和node.js都是定期进行全局gc,当发生gc时,所有代码都停止运行。如果内存申请释放很复杂,需要很长的时间才能处理完毕。程序会发生周期性卡顿。
协程调度:golang是编译为机器码执行的,如果某个协程占用的cpu时间过长,其他协程会得不到执行。这和erlang不同,erlang是运行在VM之上的,VM可以自己进行调度。机器码调度只能靠CPU中断,而且一般只有操作系统有调度的权力。Golang中只能依赖手工在代码中加入Yield(),转让CPU时间片。

Node.js

单进程单线程,全异步,性能很好。但应用层不能有阻塞,需要写大量嵌套回调才能实现业务逻辑。开发者心智负担太高。随着业务代码增加,开发和维护越来越困难。

Erlang

erlang也是协程+通道,和golang相同。Erlang确实是网络编程的终极方案,而且erlang是基于VM执行的,不存在golang的2个问题。erlang是函数式编程,比较难入门,开发效率不高。

php+libevent

libevent只是一个事件轮询的库,epoll的封装。libevent过于底层,不是整套方案,你需要自行写大量底层代码。维护成本很高,另外一旦出了问题,你需要非常熟悉libevent才能解决。

Swoole

swoole与node.js/react之类不同的是,swoole是整体方案。从性能、异步、并行、可维护性、开发效率方面综合考虑的。swoole中你可以同步,也可以异步。代码中可以有阻塞,通过多开进程来调整处理能力。

既保证性能,又保证开发效率。

分类: Swoole扩展 标签:

PHP实现真正的异步MySQL

2013年11月7日 1 条评论

node.js之类的语言可以实现异步的数据库查询功能,执行SQL语句之后不必等待数据库返回结果。继续去执行其他的代码,当数据库返回结果是再对数据进行处理,如渲染页面,并将HTML页面发送给客户端。这样应用程序完全不需要阻塞等待。这种方式运行效率非常高。

PHP中是否可以实现类似的异步非阻塞MySQL查询呢?使用github搜索发现一个项目貌似是做此功能的,https://github.com/kaja47/async-mysql,查看代码是基于React.PHP.的。但并不是真正的异步SQL。实现的原理是设置一个定时器,每0.02秒轮询一次。虽然也可以用,但这样很浪费CPU资源。不是真正的异步MYSQL。

现在Swoole1.6.2提供了swoole_event_add和swoole_get_mysqli_sock 2个新的函数,使用它完全可以实现真正的PHP异步MySQL。下面讲一下具体的实现。

代码:


$db = new mysqli;
$db->connect('127.0.0.1', 'root', 'root', 'test');
$db->query("show tables", MYSQLI_ASYNC);
swoole_event_add(swoole_get_mysqli_sock($db), function($db_sock) {
global $db;
$res = $db->reap_async_query();
var_dump($res->fetch_all(MYSQLI_ASSOC));
swoole_event_exit();
});

首先连接mysql,连接成功后执行SQL语句,要在第二个参数指定MYSQLI_ASYNC。表示此查询为异步。query函数会立即返回,这时候并没有得到mysqli_result。

然后调用swoole_get_mysqli_sock函数取到mysql连接的socket,并调用swoole_event_add将它加入到swoole的epoll事件循环中。当数据库返回结果是会回调我们刚才制定的函数。

这时候再调用mysqli_reap_async_query得到result,调用fetch_all函数得到数据。

这个过程是完全异步非阻塞的,不存在任何浪费CPU的代码。这个代码还可以用在服务器端程序上,具体代码实现后续会再写一篇文章详细介绍。

 

分类: PHP, Swoole扩展 标签:

使用swoole扩展开发PHP全异步代理服务器

2013年10月31日 2 条评论

客户端socket连接到ProxyServer后,创建一个socket连接到后端Server。并监听后端Server的回包。 客户端向ProxyServer发送请求包,proxy服务器会透明地将此包发往后端Server。
当后端Server返回包时,找到对应的客户端socket,向此socket发送回应的数据包。
此程序将swoole扩展提供的Server和Client结合在一起使用, ProxyServer对客户端连接来说是服务器端,但对backend服务器来说是客户端。
所有网络IO都是基于epoll事件循环,全异步非阻塞的。性能非常高。 长连接可以达到 10万qps的处理能力。

这里对后端Server和客户端之间使用了1对1的模式,可以改进为n对1连接池的模式,节约backend Server的TCP连接数量。当onReceve收到客户端请求时,将客户端连接与backend连接绑定。返回响应数据后解除绑定,使连接可供下一个客户端使用。

 
<?php
class ProxyServer
{
    protected $clients;
    protected $backends;
    protected $serv;

    function run()
    {
        $serv = swoole_server_create("127.0.0.1", 9509);
        swoole_server_set($serv, array(
            'timeout' => 1, //select and epoll_wait timeout.
            'poll_thread_num' => 1, //reactor thread num
            'worker_num' => 1, //reactor thread num
            'backlog' => 128, //listen backlog
            'max_conn' => 10000,
            'dispatch_mode' => 2,
            //'open_tcp_keepalive' => 1,
            'log_file' => '/tmp/swoole.log', //swoole error log
        ));
        swoole_server_handler($serv, 'onWorkerStart', array($this, 'onStart'));
        swoole_server_handler($serv, 'onConnect', array($this, 'onConnect'));
        swoole_server_handler($serv, 'onReceive', array($this, 'onReceive'));
        swoole_server_handler($serv, 'onClose', array($this, 'onClose'));
        swoole_server_handler($serv, 'onWorkerStop', array($this, 'onShutdown'));
        //swoole_server_addtimer($serv, 2);
        #swoole_server_addtimer($serv, 10);
        swoole_server_start($serv);
    }

    function onStart($serv)
    {
        $this->serv = $serv;
        echo "Server: start.Swoole version is [" . SWOOLE_VERSION . "]\n";
    }

    function onShutdown($serv)
    {
        echo "Server: onShutdown\n";
    }

    function onClose($serv, $fd, $from_id)
    {
        //backend
        if (isset($this->clients[$fd])) {
            $backend_client = $this->clients[$fd]['socket'];
            unset($this->clients[$fd]);
            $backend_client->close();
            unset($this->backends[$backend_client->sock]);
            echo "client close\n";
        }
    }

    function onConnect($serv, $fd, $from_id)
    {
        $socket = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
        echo microtime() . ": Client[$fd] backend-sock[{$socket->sock}]: Connect.\n";
        $this->backends[$socket->sock] = array(
            'client_fd' => $fd,
            'socket' => $socket,
        );
        $this->clients[$fd] = array(
            'socket' => $socket,
        );
        $socket->on('connect', function ($socket) {
            echo "connect to backend server success\n";
        });
        $socket->on('error', function ($socket) {
            echo "connect to backend server fail\n";
        });
        $socket->on('receive', function ($socket) {
            swoole_server_send($this->serv, $this->backends[$socket->sock]['client_fd'], $socket->recv());
        });
        $socket->connect('127.0.0.1', 9501, 0.2);
    }

    function onReceive($serv, $fd, $from_id, $data)
    {
        echo microtime() . ": client receive\n";
        $backend_socket = $this->clients[$fd]['socket'];
        $backend_socket->send($data);
        echo microtime() . ": send to backend\n";
        echo str_repeat('-', 100) . "\n";
    }
}

$serv = new ProxyServer();
$serv->run();
分类: Swoole扩展 标签:

Swoole压测:如何做到并发10万TCP连接

2013年8月6日 2 条评论

Server代码调整

  • 要关闭输出,否则打印屏幕会拖垮Server.
  • 修改server的max_conn参数为100000或者更大,为了安全实际上只能到99999。

内核参数调整

  • ulimit -n 要调整为100000甚至更大。如何调这个参数,可以参考网上的一些文章。命令行下执行 ulimit -n 100000即可修改。如果不能修改,需要设置  /etc/security/limits.conf,加入
    * hard nofile 100000
    * soft nofile 100000
  • net/ipv4/ip_local_port_range 需要修改为更大的范围,如20000 – 60000,否则端口号会不够用。导致客户端报 99错误
  • 单台是不能压到10万的。客户端连接需要分配一个local port。范围是上一条设置的。也就是只有4万个port可以用。可以使用多台机器来测试。
  • net.ipv4.tcp_tw_recycle = 1 ,设置快速回收被close的端口。否则端口会不够用
  • 其他内核参数的调整,如tcp_fin_timeout、tcp_keepalive_time、tcp_max_syn_backlog可以搜索网上的文章。
  • 长期运行的Server要有坏连接检测的能力,比如心跳、活动时间检测等方案。避免客户端没有发REST,导致坏连接堆积
分类: C/C++, Swoole扩展 标签:

关于Swoole编码风格

2013年8月5日 没有评论

一、大括号独占一行

有些人觉得放在同一行更好更紧凑,我个人不喜欢这种风格。代码密密麻麻挤在一起严重影响美观,可读性也不够好。

独占一行,代码就很稀疏。没那么累。

另外if 语句如果只有一行,我也习惯性写成有括号的。

if (a > b)
{
    return false;
}

二、使用下划线分割

swoole的C代码里都用swServer_这样来命名函数,然后用下划线分割。

swoole用了面向对象的风格来写C代码,方法名都很短。比如锁的代码

swMutex m = swMutex_create(&m, 1);
m.lock();
m.unlock();

三、源代码目录结构

swoole源代码使用两层目录结构,最外层是代码分类,比如是memory、factory、lock等。里面的每个.c文件都是具体的实现代码。如lock下有mutex.c、rwlock.c等。

四、少用宏

swoole里还是使用了宏的,宏是预编译期执行的,不会生成额外的指令。有些地方使用宏很方便。但swoole里不推荐使用宏来编写逻辑代码。

五、使用goto

goto其实可以减少很多if else while块,很方便。少量使用goto还是有助于提升开发效率的。但不滥用。

分类: C/C++, Swoole扩展 标签: