curl利用socket5访问国外网站

一般来说,当你访问国外的网站的时候,因为你懂的原因是无法访问的。现在我们利用curl来封装一个可以访问的库来实现可访问被封的网站!

1:首先你要在海外搭建一个自己的sokcet5协议的代理.这个略过.

2:在国内任意一台主机上搭建socket5协议的客户端服务。

一个简单的get方法如下:

function get_curl_socket5($url){
    $curl = curl_init (); // 启动一个CURL会话
    curl_setopt($curl, CURLOPT_URL,$url);
    curl_setopt($curl, CURLOPT_HEADER, 0);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_TIMEOUT,60);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($curl, CURLOPT_MAXREDIRS, 3);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
    curl_setopt($curl, CURLOPT_USERAGENT,'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0');
    curl_setopt($curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); // 设置代理类型
    curl_setopt($curl, CURLOPT_PROXY, '127.0.0.1'); // 设置代理IP
    curl_setopt($curl, CURLOPT_PROXYPORT, 1080); // 设置代理端口
    $result = curl_exec($curl);
    // 检查是否有错误发生
    if(curl_errno($curl))
    {
        echo 'Curl error: ' . curl_error($curl);
    }
    curl_close($curl);
    return $result;
}

好了,就总结到这时,大家可以继续丰富更多的方法!

发表在 好文推荐 | 标签为 , | 12 条评论

利用redis的有序集合来实现群内活跃用户排行榜

    一般社交软件都有群用户活跃度排行,简单的算法可以根据群内用户说话次数来做为判定标准,可具体代码怎么实现呢.

    比较笨的办法是在数据库中实现,可以根据消息记录来定时更新群成员表中说话次数字段,这种方式用户稍微一多肯定就抗不住了。

    现在,我们可以利用redis的有序集合来实现。

class Rank {

    private $redis = false;

    function __construct($host='127.0.0.1',$port=6379) {
        $this->redis = new Redis();
        $this->redis->connect($host,$port);
    }

    public function rank_zincrby($key,$num,$member){
        return $this->redis->zincrby($key,$num,$member);
    }

    public function rank_get_top($key,$top_num=0){
        return $this->redis->zrevrange($key,0,$top_num,1);
    }

    function __destruct() {
        //测试完毕,清除数据
        $this->redis->del('group_1');
        $this->redis->del('group_2');
        $this->redis->del('group_3');
    }

}


$rank = new Rank();

//用户a在群group_1,group_2,group_3分别说话1次
$rank->rank_zincrby("group_1",1,'a');
$rank->rank_zincrby("group_2",1,'a');
$rank->rank_zincrby("group_3",1,'a');

//用户b在群group_1说话3次
$rank->rank_zincrby("group_1",1,'b');
$rank->rank_zincrby("group_1",1,'b');
$rank->rank_zincrby("group_1",1,'b');

//用户c在群group_2说话3次
$rank->rank_zincrby("group_2",1,'c');
$rank->rank_zincrby("group_2",1,'c');
$rank->rank_zincrby("group_2",1,'c');

//用户d在群group_3说话3次
$rank->rank_zincrby("group_3",1,'d');
$rank->rank_zincrby("group_3",1,'d');
$rank->rank_zincrby("group_3",1,'d');

//分别得到三个群内说话最多的一个成员和说话次数
var_dump($rank->rank_get_top('group_1'));
var_dump($rank->rank_get_top('group_2'));
var_dump($rank->rank_get_top('group_3'));

简单记录下,以备以后需要!

发表在 网站开发 | 标签为 | 13 条评论

用phing和phpunit在发布的时候做单元测试

首先说下本文用的两个工具:

1:https://www.phing.info/

    Phing是一个PHP项目构建系统或建立一个基于工具的Apache Ant.你可以做任何它,你可以做一个传统的构建系统,如GNU make工具,它的使用简单的XML构建文件和可扩展PHP的“任务”类使其易于使用和高度灵活的构建框架

2:http://www.phpunit.cn/

    PHPUnit是一个面向PHP程序员的测试框架,这是一个xUnit的体系结构的单元测试框架

现在开始正题,我们先构建自己的发布publish.xml,大致如下:

<?xml version="1.0" encoding="UTF-8"?>
<project name="test" default="publish">

    <property name="NAME" value="test" />
    <property name="MODULE" value="test" />
    <property name="RSYNC" value="/usr/bin/rsync" />
    <php expression="date('Y-m-d-H-i-s')" returnProperty="TIME"/>
    <php expression="date('YmdHis')" returnProperty="VERSION" />
    <property name="HISTORY_DIR" value="/publish/publish_history/${NAME}/${TIME}"/>

    <!---从git拉取最新代码-->
    <target name="pull">
        <echo msg="Fulling latest code from repository, this might take a few minutes."/>
        <mkdir dir="${HISTORY_DIR}" mode="0754"/>
        <exec level="info" command="cd /publish/${NAME} &amp;&amp; git pull origin master" checkreturn="true" logoutput="true"/>
    </target>

    <!---生成要发布的代码快照-->
    <target name="rsync_local" depends="pull">
        <echo msg="Rsyncing files to publish_history folder" />
        <exec level="info" command="cd /publish/${NAME} &amp;&amp; ${RSYNC} -avzR --progress --exclude-from=${EXCLUDE_FILE} * ${HISTORY_DIR}" checkreturn="true" logoutput="false" />
        <exec level="info" command="cd ${HISTORY_DIR} &amp;&amp; echo ======${VER_DESC}====== >> ./version.txt" checkreturn="true" logoutput="false" />
        <exec level="info" command="cd /publish/${NAME} &amp;&amp; git log --stat -1 --no-merges >> ${HISTORY_DIR}/version.txt" checkreturn="true" logoutput="false"/>
    </target>

    <!---替换测试域名为正式域名-->
    <target name="replace" depends="rsync_local">
        <echo msg="Replacing special string apitest.xtgxiso.com to api.xtgxiso.com" />
        <exec level="info" command="grep -rl 'apitest.xtgxiso.com' ${HISTORY_DIR} | while read file ; do sed -i 's/apitest\.xtgxiso\.com/api.xtgxiso.com/g' $file; done" checkreturn="true" logoutput="true"/>
    </target>

    <!---替换常量为动态时间,防止CDN缓存-->
    <target name="update" depends="replace">
        <echo msg="Change css version to now timestamp" />
        <exec level="info" command="grep -rl '#VERSION#' ${HISTORY_DIR} | while read file ; do sed -i 's/#VERSION#/${VERSION}/g' $file; done" checkreturn="true" logoutput="true"/>
    </target>

    <!---删除git代码库系统文件-->
    <target name="clean" depends="update">
        <echo msg="Cleaning some no need files" />
        <exec level="info" command="cd ${NAME} &amp;&amp; find ./ -name '.gitignore' | xargs rm -f" checkreturn="true" logoutput="true"/>
    </target>

    <!---将代码发布到执行单元测试的线上机器-->
    <target name="rsync_remote" depends="clean">
        <echo msg="Rsyncing updated files to remote host" />
        <exec level="info" command="cd ${HISTORY_DIR} &amp;&amp; ${RSYNC} -avzR --progress --exclude-from '/publish/exclude.txt'  * root@192.168.1.128::test/" checkreturn="true" logoutput="false"/>
        <exec level="info" command="cd ${HISTORY_DIR} &amp;&amp; ${RSYNC} -v root@192.168.1.128::test/" checkreturn="true" logoutput="true"/>
    </target>

    <!---执行单元测试,如果通过,继续,否则停止发布-->
    <target name="phpunit" depends="rsync_remote">
        <echo msg="phpunit start" />
        <ssh failonerror="true" username="test" password="test123456" host="192.168.1.128" port="22" display="true" command="/shell/phpunit.php" />
    </target>

    <!---发布到线上-->
    <target name="rsync_product" depends="phpunit">
        <echo msg="Rsyncing updated files to rsync_product host" />
        <exec level="info" command="cd ${HISTORY_DIR} &amp;&amp; ${RSYNC} -avzR --progress --exclude-from '/publish/exclude.txt'  * root@192.168.1.129::test/" checkreturn="true" logoutput="false"/>
        <exec level="info" command="cd ${HISTORY_DIR} &amp;&amp; ${RSYNC} -v root@192.168.1.129::test/" checkreturn="true" logoutput="true"/>
    </target>

    <target name="publish" depends="rsync_product">
    </target>

</project>

2:在执行单元测试的主机上写要布时运行的脚本(/shell/phpunit.php),内容大致如下:

#!/usr/local/php7/bin/php
<?php
        $output = shell_exec('/shell/phpunit.sh');
        $pos = stripos($output,"Failures");
        if ($pos === false) {
                fwrite(STDOUT,$output."\n");
        }else{
                fwrite(STDOUT,'<font color="red">'.$output."</font>\n");
                fwrite(STDERR,$output."\n");

        }

3:phpunit.sh脚本内容如下:

#!/bin/bash
cd /www/test/tests/
/usr/local/php7/bin/php /usr/local/bin/phpunit --stop-on-failure --verbose --debug

4:发布脚本如下(new_publish.sh):

#!/bin/bash

PHING='/usr/bin/phing';

display_usage(){
        echo "Usage:" $0 "<xml file>  <version description>";
}
if [[ $1 =~ '^-(h|-help)$' ]] || [ $# -eq 0 ] || [ $# -lt 2 ];
then
        display_usage
        exit
fi

BUILDXML=$1
TARGET=$3

if [[ ! -x $PHING ]];
then
        echo -e "\e[1;31mError:\e[0m phing does not exist.";
        exit;
fi

if [[ ! -f $BUILDXML ]];
then
    echo -e "\e[1;31mError:\e[0m There is no build.xml[$BUILDXML].";
    exit;
fi

$PHING -f $BUILDXML -DVER_DESC=$2

这样我们就配置好了简单的发布和单元测试集成系统.

发布命令如下:

./new_publish.sh build.xml test

发表在 好文推荐, 网站开发, 网站架构 | 标签为 , | 15 条评论

php7下安装event扩展

Event是什么

This is an extension to efficiently schedule I/O, time and signal based events using the best I/O notification mechanism available for specific platform. This is a port of libevent to the PHP infrastructure

1:首先安装支持库libevent,不能用yum,要自己编译高版本的

wget https://github.com/libevent/libevent/releases/download/release-2.0.22-stable/libevent-2.0.22-stable.tar.gz
tar -zxvf libevent-2.0.22-stable.tar.gz
cd libevent-2.0.22-stable
./configure --prefix=/usr/local/libevent-2.0.22
make
make install

2:编译扩展

wget https://pecl.php.net/get/event-2.0.4.tgz
tar -zxvf event-2.0.4.tgz
cd event-2.0.4
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config --with-event-libevent-dir=/usr/local/libevent-2.0.22/
make
make install

3:修改php.ini增加event.so文件即可

下篇文章再写如何使用event扩展

发表在 好文推荐, 网站开发 | 标签为 , | 14 条评论

PHP7升级过程经验分享

    PHP可以在一台主机上安装多个版本,我们先配置好PHP7的环境,PHP-FPM监听不同的端口,然后就可以尝试将部份业务切到PHP7下测试。

    1:先安装系统类库

    

yum -y install libxml2 libxml2-devel openssl openssl-devel curl-devel libjpeg-devel libpng-devel freetype-devel libmcrypt-devel

    

    2:下载并安装PHP7

    

wget http://cn2.php.net/get/php-7.0.0.tar.gz/from/this/mirror -O php-7.0.0.tar
tar -zxvf php-7.0.0.tar
cd php-7.0.0
./configure --prefix=/usr/local/php7 \
--enable-fpm \
--enable-mbstring \
--enable-pcntl \
--enable-sockets \
--with-mysqli \
--with-pdo-mysql \
--enable-mysqlnd \
--with-gd \
--with-jpeg-dir \
--with-freetype-dir \
--with-iconv \
--with-openssl \
--with-curl \
--enable-opcache \
--with-mcrypt    \
--enable-shared  \
--enable-xml    \
--enable-session
make
make install

     3:安装redis扩展

    

wget https://github.com/phpredis/phpredis/archive/php7.zip -O php7_reids.zip
unzip php7_reids.zip
cd phpredis-php7/
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

    4:安装mongodb扩展

    

wget https://pecl.php.net/get/mongodb-1.1.6.tgz
tar -zxvf mongodb-1.1.6.tgz
cd mongodb-1.1.6
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

    由于新的扩展和原来的扩展使用大不一样,这里推荐这个库!

   https://github.com/mongodb/mongo-php-library

   http://mongodb.github.io/mongo-php-library/

    

    5:安装libevent扩展

    

wget https://github.com/expressif/pecl-event-libevent/archive/master.zip -O pecl-event-libevent.zip
unzip pecl-event-libevent.zip
cd pecl-event-libevent-master/
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

    6:php.ini配置

   

cp php.ini-production php.ini
#在php.ini增加发下几行
extension=redis.so
extension=libevent.so
extension=mongodb.so

#启用opcache
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1

    7:php-fpm配置

    

mv php-fpm.conf.default php-fpm.conf
mv php-fpm.d/www.conf.default php-fpm.d/www.conf

#php-fpm.conf修改
daemonize = yes

#www.conf修改
listen = 127.0.0.1:9900
listen.allowed_clients = 127.0.0.1
user = apache
group = apache
pm.max_children = 100
pm = dynamic
pm.start_servers = 20
pm.min_spare_servers = 20
pm.max_spare_servers = 50
pm.max_requests = 100000
request_slowlog_timeout = 6s
slowlog = log/$pool.log.slow

   

    8:脚本启动和任务配置

    

#创建log目录
mkdir /usr/local/php7/log
#启动
/usr/local/php7/sbin/php-fpm
#重启
kill -USR2 `cat /usr/local/php7/var/run/php-fpm.pid`
#配置任务重启
01 01 * * * kill -USR2 `cat /usr/local/php7/var/run/php-fpm.pid`

    简单总结如上!

发表在 好文推荐, 网站开发, 网站架构 | 标签为 , , , | 14 条评论

用nginx的ssl协议来代理workerman的tcp协议以及stream_socket_client测试代码

nginx来代理tcp,需要注意以下几点

1:nginx版本必须大于1.9.0

2:编译的时候必须加上这两个参数 –with-stream –with-stream_ssl_module

具体细节配置见:https://www.nginx.com/resources/admin-guide/nginx-tcp-ssl-termination/

workerman的tcp服务见:http://doc3.workerman.net/getting-started/simple-example.html  示例三

为了对比,先示例一般的TCP代码示例

<?php
$fp = stream_socket_client("tcp://127.0.0.1:1215", $errno, $errstr, 3);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    echo fgets($fp, 1024);
    fclose($fp);
}

对于服务端启用了ssl的,由于证书是自己生成的,所以需要不检查证书。如果是合法的证书,只需要将tcp协议改成ssl即可

<?php

$context = stream_context_create();
//Require verification of peer name
stream_context_set_option($context, 'ssl', 'verify_peer_name', false);
//是否需要验证 SSL 证书
stream_context_set_option($context, 'ssl', 'verify_peer', false);
//tcp不需要,在访问https的可能用的到
stream_context_set_option($context, 'ssl', 'verify_host', false);
$fp = stream_socket_client("ssl://127.0.0.1:1215", $errno, $errstr, 3,STREAM_CLIENT_CONNECT, $context);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    echo fgets($fp, 1024); 
    fclose($fp);
}

这样我们就可以将正常的tcp服务通过ssl来传输了,如果想验证可以用stream_socket_client的代码来抓包看一下,本人确定已经是加密包了

发表在 好文推荐, 网站架构 | 标签为 , , | 15 条评论

openresty负载均衡下的特殊应用–特定请求转发所有后端主机

一般的负载均衡是将请求转发给其中的一个后端主机,而我们想要实现的是将特定请求转发给所有的后端主机。

这种场景什么时候会用呢,比如:更新缓存,刷新配置等。

需要用的openresty模块

https://github.com/openresty/lua-upstream-nginx-module    

https://github.com/pintsized/lua-resty-http

配置文件大致如下:

upstream weblist {
    server 192.168.1.2:80 weight=1;
    server 192.168.1.3:80 weight=1;
}

server {
        listen    8080 default_server;
        server_name  _;
        location /cache {
                default_type text/html;
                content_by_lua_block {
                        local http = require "resty.http"
                        local httpc = http.new()
                        local upstream = require "ngx.upstream"
                        local get_servers = upstream.get_servers
                        local srvs, err = get_servers("weblist")
                        if not srvs then
                                ngx.say("failed to get servers in upstream ", u)
                        else
                                for _, srv in ipairs(srvs) do
                                        for k, v in pairs(srv) do
                                                        if k== "addr" then
                                                              ngx.req.read_body()
                                                              local res,err
                                                              res, err = httpc:request_uri("http://"..v.."/cache" , {
                                                                                                method = ngx.var.request_method,
                                                                                                body = ngx.var.request_body
                                                                                                })
                                                               
                                                              if res.status == ngx.HTTP_OK then
                                                                  ngx.say(res.body)
                                                              end
                                                        end
                                        end
                                end
                        end
                }
        }

        location / {
                proxy_pass http://weblist;
                proxy_redirect          off;
                client_max_body_size    100m;
                client_body_buffer_size 128k;
                proxy_ignore_client_abort on;
                proxy_connect_timeout   900;
                proxy_send_timeout      900;
                proxy_read_timeout      900;
                proxy_buffer_size       4k;
                proxy_buffers           4 32k;
                proxy_busy_buffers_size 64k;
                proxy_temp_file_write_size 64k;
                proxy_next_upstream http_500 http_502 http_503 http_504;
        }
 }

这只是一个简单的实现思路和方法 ,并没有解决一些细节问题(比如:请求的参数处理,响应的合并处理)。

发表在 好文推荐, 网站开发, 网站架构 | 标签为 | 18 条评论

openresty如何引用第三方 resty 库

OpenResty 引用第三方 resty 库非常简单,只需要将相应的文件拷贝到 resty 目录下即可。

我们以resty.http( https://github.com/pintsized/lua-resty-http) 库为例。

只要将lua-resty-http/lib/resty/目录下的 http.lua 和 http_headers.lua两个文件拷贝到/usr/local/openresty/lualib/resty目录下即可(假设你的openresty安装目录为/usr/local/openresty)。

验证代码如下:

erver {

    listen       8080 default_server;
    server_name  _;

    resolver 8.8.8.8;

    location /baidu {
        content_by_lua_block {
            local http = require "resty.http"
            local httpc = http.new()
            local res, err = httpc:request_uri("http://www.baidu.com")
            if res.status == ngx.HTTP_OK then
                ngx.say(res.body)
            else
                ngx.exit(res.status)
            end
        }
    }
}

访问 http://127.0.0.1:8080/baidu , 如果出现的是百度的首页,说明你配置成功了。

当然这里也可以自定义 lua_package_path 指定 Lua 的查找路径,这样就可以把第三方代码放到相应的位置下,这么做更加方便归类文件,明确什么类型的文件放到什么地方(比如:公共文件、业务文件)。

见电子书地址:https://moonbingbing.gitbooks.io/openresty-best-practices/content/ngx_lua/how_use_third_lib.html

发表在 好文推荐, 网站开发, 网站架构 | 标签为 | 18 条评论

PHP的非阻塞或并行请求实现方式

    我们都知道,php是串行请求的,我们在碰到以下几个场景的时候,php的效率会变的比较低下:

    1:一个请求,在输出结果前,有比较长时间的耗时操作(但这个操作不影响输出结果),这时候,客户端会等待比较长的时候。

    2:如果我要同时获得多个远程接口的结果,耗时是所有接口响应耗时之和那么。

    有没有办法来提升上述场景的效率呢?

    答案是肯定的

1::针对第一个场景,若你使用的是FastCGI模式 ,使用fastcgi_finish_request()能把结果输出到客户端,但PHP进程继续在跑

<?php
fastcgi_finish_request();
echo "xtgxiso"//这儿结束之后的执行

备注:此方式不能算是非阻塞,只是把结果尽快输出到客户端,但本身这个php进程还是被阻塞占用着

2:使用curl_multi_init的方法

<?php
$time = time();
// 创建一对cURL资源
$ch1 = curl_init();
$ch2 = curl_init();
$ch3 = curl_init();

// 设置URL和相应的选项
curl_setopt($ch1, CURLOPT_URL, "http://test.xtgxiso.cn/sleep1.php");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://test.xtgxiso.cn/sleep2.php");
curl_setopt($ch2, CURLOPT_HEADER, 0);
curl_setopt($ch3, CURLOPT_URL, "http://test.xtgxiso.cn/sleep3.php");
curl_setopt($ch3, CURLOPT_HEADER, 0);

// 创建批处理cURL句柄
$mh = curl_multi_init();

// 增加2个句柄
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);
curl_multi_add_handle($mh,$ch3);

$running=null;
// 执行批处理句柄
do {
    usleep(10000);
    curl_multi_exec($mh,$running);
} while ($running > 0);

// 关闭全部句柄
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_remove_handle($mh, $ch3);
curl_multi_close($mh);

echo "\n total time : ".(time()-$time)."\n";

3:使用stream_set_blocking方法

// url数组,每个url发送一个请求
$urls = array(
    "sleep1.php",
    "sleep2.php",
    "sleep3.php");

// 保存socket的数组
$sockets = array();

$time = time();
// 批量创建链接并发送数据
foreach($urls as $url)
{
    $socket = stream_socket_client("tcp://test.xtgxiso.cn:80", $errno, $errstr, 3);
    // 设置成非阻塞
    stream_set_blocking($socket, 0);
    fwrite($socket, "GET /{$url} HTTP/1.0\r\nHost: test.xtgxiso.cn\r\nAccept: */*\r\n\r\n");
    // 记录数组
    $sockets[(int)$socket] = $socket;
}

// 批量等待数据返回
while(count($sockets)>0)
{
    $read = $sockets;
    $write = $e = array();
    // 等待数据可读
    if(stream_select($read, $write, $e, 10))
    {
        // 循环读数据
        foreach($read as $socket)
        {
            // 这里是服务端返回的数据,需要的话可以循环读
            echo fread($socket, 8192)."\n";
            // 数据读取完毕关闭链接,并删除链接
            fclose($socket);
            unset($sockets[(int)$socket]);
        }
    }
}

echo time()-$time;
echo "\n";

上面两种方式请求多个url的时候,时间由原来的所有请求响应时间之和变为只是最长的那个请求的响应时间(如请求1:10ms,请求2:15ms,请求3:20ms,串行处理的时间将是:45ms,而并行处理的时间只有:25ms)

简单总结如上几种方式,这样的好处当需要请求多个url的时候,可以节省时间,从而提高效率和并发,我们可以封装个get请求的方法,post方法和get类似!

function http_get_url($arr=''){
    if ( is_array($arr) && $arr ){
        $sockets = array();
        foreach($arr as $url)
        {
            $url_info = parse_url($url);
            if ( !@$url_info["port"] ){
                $url_info["port"] = 80;
            }
            $socket = stream_socket_client("tcp://".$url_info["host"].":".$url_info["port"], $errno, $errstr,3);
            if ( $socket ) {
                stream_set_blocking($socket, 0);
                $str = "GET ".$url_info["path"]."?".$url_info["query"]." HTTP/1.0\r\nHost: ".$url_info["host"]."\r\nAccept: */*\r\n\r\n";
                echo $str;
                fwrite($socket,$str);
                $sockets[(int)$socket] = $socket;
            }
        }

        while(count($sockets)>0)
        {
            $read = $sockets;
            $write = $e = array();
            if(stream_select($read, $write, $e, 3))
            {
                foreach($read as $socket)
                {
                    $result[(int)$socket] .= fread($socket, 8192);
                    unset($sockets[(int)$socket]);
                }
            }
        }
        foreach ($result as  $k=>$v) {
            $result[$k] = trim(strstr($v,"\r\n\r\n"),"\r\n");
        }

        return $result;
    }else if ( $arr ){
        $curl = curl_init (); 
        curl_setopt($curl, CURLOPT_URL,$arr);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_TIMEOUT_MS,3000);
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($curl, CURLOPT_MAXREDIRS, 3);
        $result = curl_exec($curl);
        curl_close($curl);
        return $result;
    }else{
        return false;
    }
}

这个方法可以get一个或多个url,尤其在多个的时候,可以提高效率!

备注:

1:curl_multi和stream_select都是调用系统的select进行多路I/O利用

2:并行请求的场景,用swoole也更合适,fpm里,通过swoole_client把url发送到swoole的server,swoole_server天然支持并行请求,把汇总的结果返回到fpm.

http://www.swoole.com/

发表在 好文推荐, 网站开发 | 标签为 , | 19 条评论

php利用Streams扩展来写server

1:一个最简单的tcp服务

$socket = stream_socket_server("tcp://0.0.0.0:1215", $errno, $errstr);
if (!$socket) {
    echo "$errstr ($errno)<br />\n";
} else {
    while ($conn = stream_socket_accept($socket)) {
        fwrite($conn, 'The local time is ' . date('Y-m-d H:i:s) . "\r\n");
        fclose($conn);
    }
    fclose($socket);
}

2:一个最简单的udp服务

$socket = stream_socket_server("udp://127.0.0.1:1215", $errno, $errstr, STREAM_SERVER_BIND);
if (!$socket) {
    die("$errstr ($errno)");
}
do {
    $pkt = stream_socket_recvfrom($socket, 1, 0, $peer);
    echo "$peer\r\n";
    stream_socket_sendto($socket,'The local time is ' . "\r\n", 0, $peer);
} while ($pkt !== false);

3:一个简单的http服务

$server = stream_socket_server('tcp://0.0.0.0:1215', $errno, $errstr);

while(true)
{
    $buffer = '';
    print "waiting...";
    $client = stream_socket_accept($server);
    print "accepted " . stream_socket_get_name( $client, true) . "\n";
    if( $client )
    {
        $buffer .= fread($client, 1024);
        // Respond to client
        fwrite($client,  "HTTP/1.1 200 OK\r\n"
                . "Connection: close\r\n"
                . "Content-Type: text/html\r\n"
                . "\r\n"
                . "Hello World! " . microtime(true)
                . "<pre>{$buffer}</pre>");
        fclose($client);

    } else {
        print "error.\n";
    }
}

4:一个简单的https服务

<?php

$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'local_cert', 'test.pem');
stream_context_set_option($context, 'ssl', 'local_pk', 'test.key');
stream_context_set_option($context, 'ssl', 'allow_self_signed',true);
stream_context_set_option($context, 'ssl', 'verify_peer', false);
$server = stream_socket_server('ssl://0.0.0.0:1215', $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);

while(true)
{
    $buffer = '';
    print "waiting...";
    $client = stream_socket_accept($server);
    print "accepted " . stream_socket_get_name( $client, true) . "\n";
    if( $client )
    {
        $buffer .= fread($client, 2046);
        // Respond to client
        fwrite($client,  "HTTP/1.1 200 OK\r\n"
            . "Connection: close\r\n"
            . "Content-Type: text/html\r\n"
            . "\r\n"
            . "Hello World! " . microtime(true)
            . "<pre>{$buffer}</pre>");
        fclose($client);
    } else {
        print "error.\n";
    }
}

5:用select事件来做服务

$master = array();
$socket = stream_socket_server("tcp://0.0.0.0:1215", $errno, $errstr);
if (!$socket) {
    echo "$errstr ($errno)<br />\n";
} else {
    $master[] = $socket;
    $read = $master;
    while (1) {
        $read = $master;
        $mod_fd = @stream_select($read, $_w = NULL, $_e = NULL, 10);
        echo date("Y-m-d H:i:s")."stream_select\r\n";
        if ($mod_fd === FALSE) {
            break;
        }
        for ($i = 0; $i <= $mod_fd; $i++) {
            if ( is_resource($read[$i]) ){
                echo $i." is  resource \r\n";
                if ($read[$i] === $socket) {
                    $conn = stream_socket_accept($socket);
                    fwrite($conn, "Welcome Hello! The time is ".date("Y-m-d H:i:s")."\n");
                    $master[] = $conn;
                } else {
                    $sock_data = fread($read[$i], 2048);
                    if (strlen(trim($sock_data),"\n") === 0) { // connection closed
                        echo "connection closed\n";
                        $key_to_del = array_search($read[$i], $master, TRUE);
                        fclose($read[$i]);
                        unset($master[$key_to_del]);
                        exit;
                    } else if ($sock_data === FALSE) {
                        echo "Something bad happened";
                        $key_to_del = array_search($read[$i], $master, TRUE);
                        unset($master[$key_to_del]);
                        exit;
                    } else {
                        $str = "The client has sent :".trim($sock_data,"\n")."\n";
                        fwrite($read[$i], $str);
                        fclose($read[$i]);
                        unset($master[array_search($read[$i], $master)]);
                    }
                }
            }else{
                echo $i." is not resource \n";
            }
        }
    }
}

简单用stream扩展写了几个例子,其实它的功能可以扩展到很强大的功能。

发表在 好文推荐, 网站开发 | 标签为 | 15 条评论