swoole提供了swoole_event_add函数,可以实现异步。此函数可以用在Server或Client模式下。

swoole_event_add属于AsyncIO,必须运行在CLI 模式。

异步tcp客户端

stream_socket_client实现tcp同步客户端

示例:

<?php

$start_time = microtime(TRUE);

$fp = stream_socket_client("tcp://www.52fhy.com:80", $errno, $errstr, 30);
fwrite($fp,"GET /test.json HTTP/1.1\r\nHost: www.52fhy.com\r\n\r\n"); echo $resp = fread($fp, 8192);
fclose($fp);
echo "Finish\n"; $end_time = microtime(TRUE);
echo sprintf("use time:%.3f s\n", $end_time - $start_time);

上述代码是同步执行的。如何变成异步呢?

stream_socket_client实现tcp异步客户端

由于fread读取响应数据是同步堵塞的,我们将$fp加入到事件监听后,底层会自动将该socket设置为非阻塞模式。修改fread那一行:

swoole_event_add($fp, function($fp) {
echo $resp = fread($fp, 8192);
swoole_event_del($fp);//socket处理完成后,从epoll事件中移除socket
fclose($fp);
});
echo "Finish\n"; //swoole_event_add不会阻塞进程,这行代码会顺序执行

执行后输出:

Finish
use time:0.087 s
HTTP/1.1 200 OK
Server: AliyunOSS
Date: Sat, 21 Apr 2018 08:36:40 GMT
Content-Type: application/json
Content-Length: 26
Connection: keep-alive
x-oss-request-id: 5ADAF81884D23C965A5D2614
Accept-Ranges: bytes
ETag: "3B3B50D9C802324BB72A74FCD9060004"
Last-Modified: Sat, 21 Apr 2018 04:43:33 GMT
x-oss-object-type: Normal
x-oss-hash-crc64ecma: 9917578698767912878
x-oss-storage-class: Standard
Content-MD5: OztQ2cgCMku3KnT82QYABA==
x-oss-server-time: 5 {"url":"http://52fhy.com"}

swoole_event_add函数原型

bool swoole_event_add(mixed $sock, mixed $read_callback, mixed $write_callback = null,
int $flags = null);

$sock可以为以下四种类型:

  • int,就是文件描述符,包括swoole_client->$sockswoole_process->$pipe或者其他fd
  • stream资源,就是stream_socket_client/fsockopen创建的资源
  • sockets资源,就是sockets扩展中socket_create创建的资源,需要在编译时加入 ./configure --enable-sockets
  • object,swoole_process或swoole_client,底层自动转换为管道或客户端连接的socket

多个tcp客户端实时交互

上面的例子,已经实现了异步tcp客户端。接下来的例子会复杂些:可以在客户端A输入,客户端B能实时收到,反之也可以。

首先,我们得创建个tcp_server:

swoole_tcp_server.php

<?php 

$serv = new swoole_server('0.0.0.0', 9001);

$serv->on('Start', function(){
echo "Tcp server start. Waiting client... \n";
}); $serv->on('Connect', function($serv, $fd){
echo "New client fd:{$fd}. \n";
}); $serv->on('Receive', function($serv, $fd, $from_id, $data){
echo "Recv msg from fd:{$fd}:{$data}\n";
foreach ($serv->connections as $client) {
if($fd != $client){
$serv->send($client, $data);
}
}
}); $serv->on('Close', function($serv, $fd){
echo "Client fd:{$fd} closed. \n";
}); $serv->start();

然后实现客户端:

event_add_tcp_client.php

<?php

$socket = @stream_socket_client("tcp://127.0.0.1:9001", $errno, $errstr, 30);
if(!$socket) exit("connect server err!"); function onRead($socket){
$msg = stream_socket_recvfrom($socket, 1024);
if(!$msg){
swoole_event_del($socket);
}
echo "Recv: {$msg}\n";
fwrite(STDOUT, "ENTER MSG:");
} function onWrite($socket){
echo "onWrite\n";
} function onStdin(){
global $socket;
$msg = trim(fgets(STDIN));
if($msg == 'exit'){ //必须trim此处才会相等
swoole_event_exit();
// exit();
}
fwrite($socket, $msg);//数据量大的时候用swoole_event_write
fwrite(STDOUT, "ENTER MSG:");
} swoole_event_add($socket, 'onRead', 'onWrite');
swoole_event_add(STDIN, 'onStdin'); fwrite(STDOUT, "ENTER MSG:"); //swoole_event_add不会阻塞进程,这行代码会顺序执行

先运行服务端:

$ php swoole_tcp_server.php
Tcp server start. Waiting client...

打开两个终端,运行客户端:

$ php event_add_tcp_client.php
ENTER MSG:

效果图:

swoole_client

其实swoole已经提供了异步的swoole_client,无需使用stream_socket_*系列函数:

<?php 

$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$client->on("connect", function(swoole_client $cli) { });
$client->on("receive", function(swoole_client $cli, $data){
echo "Receive: $data";
$cli->send(str_repeat('A', 100)."\n");
sleep(1);
});
$client->on("error", function(swoole_client $cli){
echo "error\n";
});
$client->on("close", function(swoole_client $cli){
echo "Connection close\n";
});
$client->connect('127.0.0.1', 9001);

还有swoole实现的tcp/udp同步阻塞客户端:

$client = new swoole_client(SWOOLE_SOCK_TCP);
if (!$client->connect('127.0.0.1', 9001, -1))
{
exit("connect failed. Error: {$client->errCode}\n");
}
$client->send("hello world\n");
echo $client->recv();
$client->close();

swoole_client 函数原型:

swoole_client->__construct(int $sock_type, int $is_sync = SWOOLE_SOCK_SYNC, string $key);

可以使用swoole提供的宏来之指定类型,请参考 swoole常量定义

  • $sock_type表示socket的类型,如TCP/UDP
  • 使用$sock_type | SWOOLE_SSL可以启用SSL加密

    $is_sync表示同步阻塞还是异步非阻塞,默认为同步阻塞
  • $key用于长连接的Key,默认使用IP:PORT作为key。相同key的连接会被复用

php-fpm/apache环境下只能使用同步客户端。异步客户端只能使用在cli命令行环境。

异步http客户端

curl或者file_get_contents发送http请求是同步阻塞的。基于swoole_event_add封装可以实现异步。

swoole_event_add实现异步http客户端

event_add_http_client.php

<?php

class HttpClient{

    private $callback = null;

    public function __construct($url, $method = 'GET', $postfields = NULL, $headers = array()){

        //子进程发起请求
$process = new swoole_process(function(swoole_process $worker) use($url, $method, $postfields, $headers){
$response = $this->http($url, $method, $postfields, $headers);
$worker->write($response);
}, true);
$process->start(); //异步读取
swoole_event_add($process->pipe, function($pipe) use ($process){
$response = $process->read();
// print_r($response);
if(is_callable($this->callback)){
call_user_func($this->callback, $response); //回调
}
swoole_event_del($pipe);
});
} public function setCallback($callback){
$this->callback = $callback;
} /**
* http请求
*/
private function http($url, $method, $postfields = NULL, $headers = array()) {
try{
$ssl = stripos($url,'https://') === 0 ? true : false;
$ci = curl_init();
/* Curl settings */
curl_setopt($ci, CURLOPT_USERAGENT, @$_SERVER['HTTP_USER_AGENT']); //在HTTP请求中包含一个"User-Agent: "头的字符串。
curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ci, CURLOPT_TIMEOUT, 30);
curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ci, CURLOPT_ENCODING, "");
if ($ssl) {
curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在
}
curl_setopt($ci, CURLOPT_HEADER, FALSE); switch ($method) {
case 'POST':
curl_setopt($ci, CURLOPT_POST, TRUE);
if (!empty($postfields)) {
curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
}
break;
} curl_setopt($ci, CURLOPT_URL, $url );
curl_setopt($ci, CURLOPT_HTTPHEADER, $headers );
curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE ); $response = curl_exec($ci);
$httpCode = curl_getinfo($ci, CURLINFO_HTTP_CODE);
$httpInfo = curl_getinfo($ci); if (FALSE === $response)
throw new Exception(curl_error($ci), curl_errno($ci)); } catch(Exception $e) {
throw $e;
} //echo '<pre>';
//var_dump($response);
//var_dump($httpInfo); curl_close ($ci);
return $response;
}
} $client = new HttpClient('http://www.52fhy.com/test.json');
$client->setCallback(function($response){
print_r($response);
});
echo "OK\n";

运行:

$ php event_add_http_client.php
OK
{"url":"http://52fhy.com"}[

由返回结果可以看出,客户端请求是异步执行的。

swoole_http_client

Swoole也内置了http异步客户端(swoole>=1.8.0)。

相比curl和file_get_contents这样PHP提供的Http客户端,swoole_http_client最大的优势是支持大量并发。

file_get_contents只能同时请求一个URL,并发只能通过开启多进程实现。curl提供了curl_multi功能实现并发基于select和多线程。并发能力都很差。而swoole_http_client是基于epoll实现的异步客户端,没有并发限制,可在一个进程内同时并发上万请求。更多介绍详见swoole文档

示例:

get:

$cli = new swoole_http_client('www.52fhy.com', 80);
$cli->setHeaders(['User-Agent' => "swoole"]); $cli->get('/test.json', function ($cli) {
echo $cli->body;
}); echo "ok\n";

输出:

ok
{"url":"http://52fhy.com"}

post:

$cli = new swoole_http_client('127.0.0.1', 81);
$cli->post('/post_demo.php', array("a" => '1234', 'b' => '456'), function ($cli) {
echo "Length: " . strlen($cli->body) . "\n";
echo $cli->body;
}); echo "ok\n";

websocket:

<?php
$cli = new swoole_http_client('118.25.40.163', 8088); $cli->on('message', function ($_cli, $frame) {
// var_dump($frame);
echo $frame->data;
}); $cli->upgrade('/', function ($cli) {
$cli->push("hello world");
}); echo "ok\n";

发送完客户端会立即close。

参考

1、swoole_event_add

https://wiki.swoole.com/wiki/page/119.html

2、Client

https://wiki.swoole.com/wiki/page/p-client.html

3、利用swoole_process和eventloop实现php异步编程

https://segmentfault.com/a/1190000008034626

4、关于swoole_process的一些使用疑惑

https://group.swoole.com/question/106198

5、swoole多进程操作

https://blog.csdn.net/koastal/article/details/52871316

6、swoole教程第一节:进程管理模块(Process)-中(消息队列)

https://segmentfault.com/a/1190000003073389

7、PHP编程中尝试程序并发的几种方式总结

http://www.jb51.net/article/81245.htm

8、1.8.0 使用内置Http异步客户端

https://wiki.swoole.com/wiki/page/678.html

9、异步Http/WebSocket客户端

https://wiki.swoole.com/wiki/page/p-http_client.html

swoole_event_add实现异步的更多相关文章

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

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

  2. PHP异步扩展Swoole笔记(1)

    安装Swoole扩展 通过pecl安装, 系统中最好已经有http2依赖, 如果是Ubuntu, 可以直接通过apt安装nghttp2, 如果是Centos或者需要自己编译, 在Github下载ngh ...

  3. 16.swoole学习笔记--异步事件

    <?php //异步事件 $fp=stream_socket_client(); fwrite($fp,"GET / HTTP/1.1\r\nHost:www.qq.com\r\n\r ...

  4. 异步任务队列Celery在Django中的使用

    前段时间在Django Web平台开发中,碰到一些请求执行的任务时间较长(几分钟),为了加快用户的响应时间,因此决定采用异步任务的方式在后台执行这些任务.在同事的指引下接触了Celery这个异步任务队 ...

  5. C#异步编程(一)

    异步编程简介 前言 本人学习.Net两年有余,是第一次写博客,虽然写的很认真,当毕竟是第一次,肯定会有很多不足之处, 希望大家照顾照顾新人,有错误之处可以指出来,我会虚心接受的. 何谓异步 与同步相对 ...

  6. redux-amrc:用更少的代码发起异步 action

    很多人说 Redux 代码多,开发效率低.其实 Redux 是可以灵活使用以及拓展的,经过充分定制的 Redux 其实写不了几行代码.今天先介绍一个很好用的 Redux 拓展-- redux-amrc ...

  7. C#与C++的发展历程第三 - C#5.0异步编程巅峰

    系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...

  8. 关于如何提高Web服务端并发效率的异步编程技术

    最近我研究技术的一个重点是java的多线程开发,在我早期学习java的时候,很多书上把java的多线程开发标榜为简单易用,这个简单易用是以C语言作为参照的,不过我也没有使用过C语言开发过多线程,我只知 ...

  9. 异步编程 In .NET

    概述 在之前写的一篇关于async和await的前世今生的文章之后,大家似乎在async和await提高网站处理能力方面还有一些疑问,博客园本身也做了不少的尝试.今天我们再来回答一下这个问题,同时我们 ...

随机推荐

  1. 转载---SuperMap GIS 9D SP1学习视频播单

    转自:http://blog.csdn.net/supermapsupport/article/details/79219102 SuperMap GIS 9D SP1学习视频播单 我们一直在思考什么 ...

  2. wpf研究之道-datagrid控件(1)

    "想要说些什么 又不知从何说起",每当想要写一些关于wpf的文章,总是沉思良久,怕自己写不好.今天我想要说的是wpf中datagrid控件.我们先来看看它在整个类的层次结构:   ...

  3. 阿尔法冲刺——Postmortem会议

    设想与目标 1.我们软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 这个问题,我们觉得我们的软件目标还是比较明确的,在SRS中也给出了典型用户和典型场景的清晰的描述. 2 ...

  4. day9

    Alpha冲刺Day9 一:站立式会议 今日安排: 经过为期5天的冲刺,基本完成企业人员模块的开发.因第三方机构与企业存在委托的关系.第三方人员对于风险的自查.风险列表的展示以及自查风险的统计展示(包 ...

  5. PTA題目的處理(三)

    题目7-1 高速公路超速處罰 1.實驗代碼 #include <stdio.h> //#include <stdlib.h> int main() { int csp,lsp; ...

  6. 基于Unity·UGUI实现的RecycleList循环列表UI容器

    在UI功能开发实践中,列表UI容器是我们经常使用一种UI容器组件.这种组件就根据输入的数据集合生成对应数据项目.从显示的方向来说,一般就分为水平排布和垂直排布的列表容器两种.列表容器为了在有限的界面空 ...

  7. mysql5.7在windows下面的主从复制配置

    目标:自动同步Master 服务器上面的Demo数据库到Slave 服务器的Demo数据库中. 对于一些操作系统比较强而使用频率又不高的东西,往往好久不去弄就忘记了,所以要经常记录起来,方便日后查阅. ...

  8. 从PRISM开始学WPF(二)Prism?

    目录: 从PRISM开始学WPF(一)WPF? 从PRISM开始学WPF(二)Prism? 从PRISM开始学WPF(三)Prism-Region? 从PRISM开始学WPF(四)Prism-Modu ...

  9. JAVA_SE基础——43.抽象类

    高手勿喷~ 抽象类:当定义一个类时,常常需要定义一些方法来描述该类的行为特征,但有时这些方法的实现方式是无法确定的.例如定义Animal类时,shout()方法用于表示动物的叫声,但是针对不同的动物, ...

  10. LeetCode & Q268-Missing Number-Easy

    Array Math Bit Manipulation Description: Given an array containing n distinct numbers taken from 0, ...