Initial PHP experience and initial php experience

Source: Internet
Author: User

Initial PHP experience and initial php experience
PHP coroutine initial experience

By warezhou 2014.11.24

Since the last attempt to add coroutine to PHP through the C extension failed, Zend is almost zero in the short term and can only be used as a native language. After Google found that PHP5.5 introduced the new features of Generator and Coroutine, so this article was born.

Background

When C/C ++ background development encounters Coroutine

Http://blog.csdn.net/cszhouwei/article/details/14230529

A failed PHP extension development tour

Http://blog.csdn.net/cszhouwei/article/details/41290673

Prerequisites Generator
function my_range($start, $end, $step = 1) {    for ($i = $start; $i <= $end; $i += $step) {        yield $i;    }}foreach (my_range(1, 1000) as $num) {    echo $num, "\n";}/* * 1 * 2 * ... * 1000 */

Figure 1 implement range () based on generator

$range = my_range(1, 1000);var_dump($range);/* * object(Generator)#1 (0) { * } */var_dump($range instanceof Iterator);/* * bool(true) */

Figure 2 speculation about the implementation of my_range ()

Since I have been familiar with PHP for a long time and have not gone deep into the language implementation details, I can only guess based on the phenomenon. Here are some of my personal understandings:

  • A function that contains the yield keyword is special. The returned value is a Generator object. At this time, the statements in the function are not actually executed.
  • The Generator object is an Iterator interface instance, which can be manipulated through rewind (), current (), next (), and valid () interfaces.
  • Generator can be considered as an "interruptible" function, and yield forms a series of "Medium breakpoints"
  • Generator is similar to the assembly line produced in the workshop. It gets one from it every time you use the product. Then the assembly line stops there waiting for the next operation.
Coroutine

Careful readers may have discovered that as of now, Generator has implemented the key features of Coroutine: interrupted execution and resumed execution. According to the idea of "when C/C ++ backend development encounters Coroutine", it is sufficient to use the "global variables" language facility for information transmission to Implement Asynchronous Server.

In fact, compared with the swapcontext family functions, Generator has made a huge step forward, and has the ability to "return data". If both have the ability to "send data, you don't have to make a detour through the lame techniques any more. In PHP, you can use the send () interface of Generator (Note: it is no longer the next () Interface) to complete the "send data" task, thus achieving real "two-way communication ".

function gen() {    $ret = (yield 'yield1');    echo "[gen]", $ret, "\n";    $ret = (yield 'yield2');    echo "[gen]", $ret, "\n";}$gen = gen();$ret = $gen->current();echo "[main]", $ret, "\n";$ret = $gen->send("send1");echo "[main]", $ret, "\n";$ret = $gen->send("send2");echo "[main]", $ret, "\n";/* * [main]yield1 * [gen]send1 * [main]yield2 * [gen]send2 * [main] */

Figure 3 Coroutine bidirectional communication example

As a C/C ++ code farmer, after discovering "re-entry" and "two-way communication" capabilities, it seems that there is no more luxury, but PHP is generous, the Exception mechanism is added, and the "error handling" mechanism is further improved.

function gen() {    $ret = (yield 'yield1');    echo "[gen]", $ret, "\n";    try {        $ret = (yield 'yield2');        echo "[gen]", $ret, "\n";    } catch (Exception $ex) {        echo "[gen][Exception]", $ex->getMessage(), "\n";    }       echo "[gen]finish\n";}$gen = gen();$ret = $gen->current();echo "[main]", $ret, "\n";$ret = $gen->send("send1");echo "[main]", $ret, "\n";$ret = $gen->throw(new Exception("Test"));echo "[main]", $ret, "\n";/* * [main]yield1 * [gen]send1 * [main]yield2 * [gen][Exception]Test * [gen]finish * [main] */

Figure 4 Coroutine error handling example

Practical drills

The previous section briefly introduced the relevant language facilities. How should we use them in actual projects? Let's continue with the scenario described in "A failed PHP extension development journey" and use the above features to achieve that beautiful wish: Write asynchronous code in synchronous mode!

First Draft

<?phpclass AsyncServer {    protected $handler;    protected $socket;    protected $tasks = [];    public function __construct($handler) {        $this->handler = $handler;        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);        if(!$this->socket) {            die(socket_strerror(socket_last_error())."\n");        }        if (!socket_set_nonblock($this->socket)) {            die(socket_strerror(socket_last_error())."\n");        }        if(!socket_bind($this->socket, "0.0.0.0", 1234)) {            die(socket_strerror(socket_last_error())."\n");        }    }    public function Run() {        while (true) {            $reads = array($this->socket);            foreach ($this->tasks as list($socket)) {                $reads[] = $socket;            }            $writes = NULL;            $excepts= NULL;            if (!socket_select($reads, $writes, $excepts, 0, 1000)) {                continue;            }            foreach ($reads as $one) {                $len = socket_recvfrom($one, $data, 65535, 0, $ip, $port);                if (!$len) {                    //echo "socket_recvfrom fail.\n";                    continue;                }                if ($one == $this->socket) {                    //echo "[Run]request recvfrom succ. data=$data ip=$ip port=$port\n";                    $handler = $this->handler;                    $coroutine = $handler($one, $data, $len, $ip, $port);                    $task = $coroutine->current();                    //echo "[Run]AsyncTask recv. data=$task->data ip=$task->ip port=$task->port timeout=$task->timeout\n";                    $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);                    if(!$socket) {                        //echo socket_strerror(socket_last_error())."\n";                        $coroutine->throw(new Exception(socket_strerror(socket_last_error()), socket_last_error()));                        continue;                    }                    if (!socket_set_nonblock($socket)) {                        //echo socket_strerror(socket_last_error())."\n";                        $coroutine->throw(new Exception(socket_strerror(socket_last_error()), socket_last_error()));                        continue;                    }                    socket_sendto($socket, $task->data, $task->len, 0, $task->ip, $task->port);                    $this->tasks[$socket] = [$socket, $coroutine];                } else {                    //echo "[Run]response recvfrom succ. data=$data ip=$ip port=$port\n";                    if (!isset($this->tasks[$one])) {                        //echo "no async_task found.\n";                    } else {                        list($socket, $coroutine) = $this->tasks[$one];                        unset($this->tasks[$one]);                        socket_close($socket);                        $coroutine->send(array($data, $len));                    }                }            }        }    }}class AsyncTask {    public $data;    public $len;    public $ip;    public $port;    public $timeout;    public function __construct($data, $len, $ip, $port, $timeout) {        $this->data = $data;        $this->len = $len;        $this->ip = $ip;        $this->port = $port;        $this->timeout = $timeout;    }}function RequestHandler($socket, $req_buf, $req_len, $ip, $port) {    //echo "[RequestHandler] before yield AsyncTask. REQ=$req_buf\n";    list($rsp_buf, $rsp_len) = (yield new AsyncTask($req_buf, $req_len, "127.0.0.1", 2345, 1000));    //echo "[RequestHandler] after yield AsyncTask. RSP=$rsp_buf\n";    socket_sendto($socket, $rsp_buf, $rsp_len, 0, $ip, $port);}$server = new AsyncServer(RequestHandler);$server->Run();?>

Code explanation:

  • For ease of Problem description, all the underlying communication here is based on UDP, and tedious details such as TCP connect are omitted.
  • AsyncServer is the underlying framework class. It encapsulates network communication details and coroutine switching details and binds coroutine through socket.
  • RequestHandler is a service processing function and implements Asynchronous Network interaction through yield new AsyncTask ().
Improved Version 2

Issues left behind in the first version:

  • The timeout of Asynchronous Network interaction is not implemented. Only interface parameters are reserved.
  • Yield new AsyncTask () calling method is not natural enough, slightly awkward
<?phpclass AsyncServer {    protected $handler;    protected $socket;    protected $tasks = [];    protected $timers = [];    public function __construct(callable $handler) {        $this->handler = $handler;        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);        if(!$this->socket) {            die(socket_strerror(socket_last_error())."\n");        }        if (!socket_set_nonblock($this->socket)) {            die(socket_strerror(socket_last_error())."\n");        }        if(!socket_bind($this->socket, "0.0.0.0", 1234)) {            die(socket_strerror(socket_last_error())."\n");        }    }    public function Run() {        while (true) {            $now = microtime(true) * 1000;            foreach ($this->timers as $time => $sockets) {                if ($time > $now) break;                foreach ($sockets as $one) {                    list($socket, $coroutine) = $this->tasks[$one];                    unset($this->tasks[$one]);                    socket_close($socket);                    $coroutine->throw(new Exception("Timeout"));                }                unset($this->timers[$time]);            }            $reads = array($this->socket);            foreach ($this->tasks as list($socket)) {                $reads[] = $socket;            }            $writes = NULL;            $excepts= NULL;            if (!socket_select($reads, $writes, $excepts, 0, 1000)) {                continue;            }            foreach ($reads as $one) {                $len = socket_recvfrom($one, $data, 65535, 0, $ip, $port);                if (!$len) {                    //echo "socket_recvfrom fail.\n";                    continue;                }                if ($one == $this->socket) {                    //echo "[Run]request recvfrom succ. data=$data ip=$ip port=$port\n";                    $handler = $this->handler;                    $coroutine = $handler($one, $data, $len, $ip, $port);                    if (!$coroutine) {                        //echo "[Run]everything is done.\n";                        continue;                    }                    $task = $coroutine->current();                    //echo "[Run]AsyncTask recv. data=$task->data ip=$task->ip port=$task->port timeout=$task->timeout\n";                    $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);                    if(!$socket) {                        //echo socket_strerror(socket_last_error())."\n";                        $coroutine->throw(new Exception(socket_strerror(socket_last_error()), socket_last_error()));                        continue;                    }                    if (!socket_set_nonblock($socket)) {                        //echo socket_strerror(socket_last_error())."\n";                        $coroutine->throw(new Exception(socket_strerror(socket_last_error()), socket_last_error()));                        continue;                    }                    socket_sendto($socket, $task->data, $task->len, 0, $task->ip, $task->port);                    $deadline = $now + $task->timeout;                    $this->tasks[$socket] = [$socket, $coroutine, $deadline];                    $this->timers[$deadline][$socket] = $socket;                } else {                    //echo "[Run]response recvfrom succ. data=$data ip=$ip port=$port\n";                    list($socket, $coroutine, $deadline) = $this->tasks[$one];                    unset($this->tasks[$one]);                    unset($this->timers[$deadline][$one]);                    socket_close($socket);                    $coroutine->send(array($data, $len));                }            }        }    }}class AsyncTask {    public $data;    public $len;    public $ip;    public $port;    public $timeout;    public function __construct($data, $len, $ip, $port, $timeout) {        $this->data = $data;        $this->len = $len;        $this->ip = $ip;        $this->port = $port;        $this->timeout = $timeout;    }}function AsyncSendRecv($req_buf, $req_len, $ip, $port, $timeout) {    return new AsyncTask($req_buf, $req_len, $ip, $port, $timeout);}function RequestHandler($socket, $req_buf, $req_len, $ip, $port) {    //echo "[RequestHandler] before yield AsyncTask. REQ=$req_buf\n";    try {        list($rsp_buf, $rsp_len) = (yield AsyncSendRecv($req_buf, $req_len, "127.0.0.1", 2345, 3000));    } catch (Exception $ex) {        $rsp_buf = $ex->getMessage();        $rsp_len = strlen($rsp_buf);        //echo "[Exception]$rsp_buf\n";    }    //echo "[RequestHandler] after yield AsyncTask. RSP=$rsp_buf\n";    socket_sendto($socket, $rsp_buf, $rsp_len, 0, $ip, $port);}$server = new AsyncServer(RequestHandler);$server->Run();?>

Code explanation:

  • With the built-in array capability of PHP, simple "time-out management" is implemented, with millisecond as the precision of time sharding
  • Encapsulate the AsyncSendRecv interface, and the call is more natural, such as yield AsyncSendRecv ().
  • Add Exception as the error handling mechanism and add ret_code for demonstration only
Performance testing environment


Test Data
  100 Byte/REQ 1000 Byte/REQ
Async_svr_v1.php 16000/s 15000/s
Async_svr_v2.php 11000/s 10000/s
Future Prospects
  • Interested PHPer can encapsulate the underlying framework based on this idea and encapsulate common blocking operations, such as connect, send, recv, and sleep...
  • I have been familiar with PHP for a long time, and many of them are not optimal in usage. Experts can optimize them accordingly and their performance should be improved.
  • Currently, coroutine binding is based on socket. If the connection pool is too overhead for each connect/close operation based on TCP communication, you need to consider implementing the connection pool.
  • Similar language facilities are also available for python and other languages. Interested readers can study them on their own.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.