php怎么打开html页面?

 更新时间:2017年7月6日 23:33  点击:1848
本文介绍了php怎么打开html页面的代码,不会的同学快来看看吧!

直接打开就行 如: $temp=file_get_contents(Ƈ.htm '); echo $temp;

本文我们来分享一下自动化配置和管理工具saltstack mine如何通过自定义收集minion的状态及应用场景的,对minion开发感兴趣的朋友可以看一下。

mine是做什么?   就是你在mine定义了收集模块,他自己会把数据收集的,存到了哪里,不知道,但是肯定是客户端minino本地。有兴趣的朋友可以check saltstack的代码。


这里先粘贴下,官方的doc用法:

 代码如下 复制代码
mine.delete:                                                                          
                                                                                       
    Remove specific function contents of minion. Returns True on success.             
                                                                                       
    CLI Example:                                                                      
                                                                                       
        salt '*' mine.delete 'network.interfaces'                                     
                                                                                       
                                                                                       
mine.flush:                                                                           
                                                                                       
    Remove all mine contents of minion. Returns True on success.                      
                                                                                       
    CLI Example:                                                                      
                                                                                       
        salt '*' mine.flush                                                           
                                                                                       
                                                                                       
mine.get:                                                                             
                                                                                       
    Get data from the mine based on the target, function and expr_form                
                                                                                       
    Targets can be matched based on any standard matching system that can be          
    matched on the master via these keywords::                                        
                                                                                       
        glob                                                                          
        pcre                                                                          
        grain                                                                         
        grain_pcre                                                                    
                                                                                       
    CLI Example:                                                                      
                                                                                       
        salt '*' mine.get '*' network.interfaces                                      
        salt '*' mine.get 'os:Fedora' network.interfaces grain                        
                                                                                       
                                                                                       
mine.send:                                                                            
                                                                                       
    Send a specific function to the mine.                                             
                                                                                       
    CLI Example:                                                                      
                                                                                       
        salt '*' mine.send network.interfaces eth0                                    
                                                                                       
                                                                                       
mine.update:    
                                                                                       
    Execute the configured functions and send the data back up to the master          
    The functions to be executed are merged from the master config, pillar and        
    minion config under the option "function_cache":                                  
                                                                                       
        mine_functions:                                                               
          network.ip_addrs:




配置起来很简单,跑到minion端,配置下文件,开启几个模块的检测。

 代码如下 复制代码
[root@vm-10-154-252-46 utils]# tail /etc/salt/minion

mine_functions:

  test.ping: []

  network.dig:

   - www.naver.com

  network.usage: []


mine_interval: 1


然后咱们跑到master端去抓数据。


[root@vm-10-154-252-82 ~/clusterops/templates]$salt '10.154.252.46' mine.get '*' network.dig          

10.154.252.46:                                                                                        

    ----------                                                                                        

    10.154.252.46:                                                                                    

                                                                                                      

        ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.10.rc1.el6 <<>> www.baidu.com                              

        ;; global options: +cmd                                                                       

        ;; Got answer:                                                                                

        ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5279                                      

        ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 5, ADDITIONAL: 2                          

        ;; QUESTION SECTION:                                                                          

        ;www.baidu.com.                 IN      A                                                     
                                                                                           

        ;; ANSWER SECTION:                                                                            

        www.baidu.com.          186     IN      CNAME   www.a.shifen.com.                             

        www.a.shifen.com.       149     IN      A       220.181.111.188                               

        www.a.shifen.com.       149     IN      A       220.181.112.244                               

        ;; AUTHORITY SECTION:                                                                         

        a.shifen.com.           186     IN      NS      ns3.a.shifen.com.                             

        a.shifen.com.           186     IN      NS      ns1.a.shifen.com.                             

        a.shifen.com.           186     IN      NS      ns5.a.shifen.com.                             

        a.shifen.com.           186     IN      NS      ns2.a.shifen.com.                             

        a.shifen.com.           186     IN      NS      ns4.a.shifen.com.                             

        ;; ADDITIONAL SECTION:                                                                        

        ns5.a.shifen.com.       70      IN      A       119.75.219.43                                 

        ns2.a.shifen.com.       186     IN      A       180.149.133.241                               

                                                                                                      

        ;; Query time: 1 msec                                                                         

        ;; SERVER: 10.150.0.253#53(10.150.0.253)                                                      

        ;; WHEN: Wed Jul  2 14:42:55 2014                                                             

        ;; MSG SIZE  rcvd: 212                                                                        


[root@vm-10-154-252-82 ~/clusterops/templates]$         

                                              


我先前定义的是一分钟来生成一次数据。在minon的日志端是可以体现出来的。

 代码如下 复制代码
2014-07-03 09:15:05,054 [salt.loaded.int.module.mine
2014-07-03 09:15:05,186 [salt.loaded.int.module.mine
2014-07-03 09:16:05,149 [salt.loaded.int.module.mine
2014-07-03 09:16:05,274 [salt.loaded.int.module.mine
2014-07-03 09:17:05,242 [salt.loaded.int.module.mine
2014-07-03 09:17:05,366 [salt.loaded.int.module.mine
2014-07-03 09:18:05,336 [salt.loaded.int.module.mine
2014-07-03 09:18:05,463 [salt.loaded.int.module.mine
2014-07-03 09:19:05,430 [salt.loaded.int.module.mine
2014-07-03 09:19:05,554 [salt.loaded.int.module.mine
2014-07-03 09:20:05,523 [salt.loaded.int.module.mine
2014-07-03 09:20:05,648 [salt.loaded.int.module.mine
2014-07-03 09:21:05,619 [salt.loaded.int.module.mine
2014-07-03 09:21:05,748 [salt.loaded.int.module.mine
2014-07-03 09:22:05,713 [salt.loaded.int.module.mine
2014-07-03 09:22:05,843 [salt.loaded.int.module.mine
2014-07-03 09:23:05,808 [salt.loaded.int.module.mine
2014-07-03 09:23:05,938 [salt.loaded.int.module.mine
2014-07-03 09:24:05,901 [salt.loaded.int.module.mine
2014-07-03 09:24:06,026 [salt.loaded.int.module.mine
2014-07-03 09:25:05,995 [salt.loaded.int.module.mine
2014-07-03 09:25:06,120 [salt.loaded.int.module.mine
2014-07-03 09:26:05,088 [salt.loaded.int.module.mine
2014-07-03 09:26:05,214 [salt.loaded.int.module.mine
2014-07-03 09:27:05,183 [salt.loaded.int.module.mine
2014-07-03 09:27:05,319 [salt.loaded.int.module.mine
2014-07-03 09:28:05,287 [salt.loaded.int.module.mine
2014-07-03 09:28:05,418 [salt.loaded.int.module.mine
2014-07-03 09:29:05,373 [salt.loaded.int.module.mine
2014-07-03 09:29:05,502 [salt.loaded.int.module.mine
2014-07-03 09:30:05,466 [salt.loaded.int.module.mine
2014-07-03 09:30:05,591 [salt.loaded.int.module.mine
2014-07-03 09:31:05,560 [salt.loaded.int.module.mine
2014-07-03 09:31:05,689 [salt.loaded.int.module.mine
2014-07-03 09:32:05,655 [salt.loaded.int.module.mine
2014-07-03 09:32:05,785 [salt.loaded.int.module.mine
2014-07-03 09:33:05,749 [salt.loaded.int.module.mine
2014-07-03 09:33:05,874 [salt.loaded.int.module.mine
2014-07-03 09:34:05,845 [salt.loaded.int.module.mine
2014-07-03 09:34:05,969 [salt.loaded.int.module.mine
2014-07-03 09:35:05,937 [salt.loaded.int.module.mine
2014-07-03 09:35:06,068 [salt.loaded.int.module.mine
2014-07-03 09:36:05,017 [salt.loaded.int.module.mine
2014-07-03 09:36:05,141 [salt.loaded.int.module.mine
2014-07-03 09:37:05,113 [salt.loaded.int.module.mine
2014-07-03 09:37:05,238 [salt.loaded.int.module.mine
2014-07-03 09:38:05,208 [salt.loaded.int.module.mine
2014-07-03 09:38:05,336 [salt.loaded.int.module.mine
2014-07-03 09:39:05,304 [salt.loaded.int.module.mine
2014-07-03 09:39:05,428 [salt.loaded.int.module.mine
2014-07-03 09:40:05,687 [salt.loaded.int.module.mine
2014-07-03 09:40:05,810 [salt.loaded.int.module.mine
2014-07-03 09:41:05,781 [salt.loaded.int.module.mine
2014-07-03 09:41:05,908 [salt.loaded.int.module.mine


咱们也可以利用mine.send来发送数据。

 代码如下 复制代码
[root@vm-10-154-252-82 ~/clusterops/templates]$salt '10.154.252.46' mine.send network.dig youku.com      

10.154.252.46:                                                                                           

    True                                                                                                 

[root@vm-10-154-252-82 ~/clusterops/templates]$            

                                              


我们又重新定义了mine的规则。

 代码如下 复制代码

mine_functions:
  test.ping: []
  cmd.run:
   - date;df



咱们定义了有时间特征的收集,用来判断下,他获取的数据是不是一分钟间隔,你就算mine.get也只是获取到他先前生成的数据,而不是实时的。

wKioL1O0uCnAVsd2AAaAeLOLDHM598.jpg


wKioL1O0uKrTwCaUAALHxCHZpTM407.jpg


有些朋友还是再疑惑这些东西也可以用直接推送模块来实现,但是你看下面,我定义了sleep 10s,如果用模块去取值的话,一定要sleep 10秒之后,才能获取到数据。但这里master去get数据的时候,还是很即时的回来。当然取得数据是一分钟之前的。对一些场合这已经足够了。

wKiom1O0vU-y66T8AAQGxHTqjiE955.jpg

上面说了,是什么场合?   来个简单渲染haproxy配置。

 代码如下 复制代码
/srv/pillar/top.sls:

base:

  'G@roles:web':

    - web

/srv/pillar/web.sls:

mine_functions:

  network.ip_addrs: [eth0]


/etc/salt/minion.d/mine.conf:

mine_interval: 5


/srv/salt/haproxy.sls:

haproxy_config:

  file:

    - managed

    - name: /etc/haproxy/config

    - source: salt://haproxy_config

    - template: jinja


/srv/salt/haproxy_config:

<...file contents snipped...>


{% for server, addrs in salt['mine.get']('roles:web', 'network.ip_addrs', expr_form='grain').items() %}

server {{ server }} {{ addrs[0] }}:80 check

{% endfor %}


<...file contents snipped...>



根据monin客户端的一些特征来渲染配置,估计大家已经学会了用根据grains取渲染配置,但是grains的数据有些不及时,虽然也有办法让他及时。 

我这里也只是阐述下mine和grains在通过jinja2渲染配置的场景。  我自己看来,grains更适合很久很久才变化的。 mine适合变化比较大的。

大家show下saltstack grains的数据,几乎都是系统和硬件的信息。

 代码如下 复制代码
[root@vm-10-154-252-82 ~/clusterops]$salt '10.154.252.46' grains.item productname
10.154.252.46:
  productname: OpenStack Nova
[root@vm-10-154-252-82 ~/clusterops]$salt '10.154.252.46' grains.item lang
10.154.252.46:
  lang: python erlang
[root@vm-10-154-252-82 ~/clusterops]$
[root@vm-10-154-252-82 ~/clusterops]$
[root@vm-10-154-252-82 ~/clusterops]$
[root@vm-10-154-252-82 ~/clusterops]$salt '10.154.252.46' sys.reload_modules
10.154.252.46:
    True
[root@vm-10-154-252-82 ~/clusterops]$




用mine的话,咱们可以定义自己感兴趣的。 可以根据现在minon使用的负载和内存情况,来分配不同的配置选项。。。。 pillar 和 mine我想大家应该能区分开来 。

 代码如下 复制代码
[root@vm-10-154-252-82 ~/clusterops]$time salt '10.154.252.46' mine.get '*' status.meminfo|more
10.154.252.46:
    ----------
    10.154.252.46:
        ----------
        Active:
            ----------
            unit:
                kB
            value:
                506876
        Active(anon):
            ----------
            unit:
                kB
            value:
                94456
        Active(file):
            ----------
            unit:
                kB
            value:
                412420
        AnonHugePages:
            ----------
            unit:
                kB
            value:
                20480
        AnonPages:
            ----------
            unit:
                kB
            value:
                94232
        Bounce:
            ----------



总结,一定要区分直接推送模块和mine的区别,还有grains和mine的区别。 仁者见仁,智者见智。 想怎么用,还是需要你自己掌控的。

nanomsg是一个实现了几种“可扩展协议”的高性能通信库;可扩展协议的任务是定义多个应用系统如何通信,从而组成一个大的分布式系统。本文我们来讲讲nanomsg的pubsub及survey

nanomsg实验——pubsub

发布订阅模式是很多消息中间件提供的常见功能。通过消息机制,能够将消息发布者和消息接收(消费)者
进行解耦。pubsub模式也是nanomsg直接支持的一直消息模型之一,因此通过pubsub模式实验,
同时也大致了解了下nanomsg的基础用法。

服务端

 代码如下 复制代码
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;time.h&gt;
#include &lt;string.h&gt;
#include &lt;unistd.h&gt;
#include &lt;nanomsg/nn.h&gt;
#include &lt;nanomsg/pubsub.h&gt;

void usage(const char *name)
{
    fprintf(stderr, "%s [ bind url]n", name);
}

int main(int argc, char **argv)
{
    if(argc != 2) {
        usage(argv[0]);
        exit(-1);
    }

    const char *url = argv[1];
    int sock = nn_socket(AF_SP, NN_PUB);
    if(sock &lt; 0) {
        fprintf (stderr, "nn_socket failed: %sn", nn_strerror (errno));
        exit(-1);
    }

    if(nn_bind(sock, url) &lt; 0) {
        fprintf(stderr, "nn_bind failed: %sn", nn_strerror(errno));
        exit(-1);
    }

    while(1) {
        time_t rawtime;
        struct tm * timeinfo;

        time (&amp;rawtime);
        timeinfo = localtime (&amp;rawtime);
        char *text = asctime (timeinfo);
        int textLen = strlen(text);
        text[textLen - 1] = '';

        printf ("SERVER: PUBLISHING DATE %sn", text);
        nn_send(sock, text, textLen, 0);
        sleep(1);
    }

    return 0;
}



nanomsg使用非常简单,只要直接include nanomsg/nn.h,即可使用基本API。使用内置的通信模式,
需要引入对应的头文件,如pubsub模式,引入nonomsg/pubsub.h即可。

pubsub server,需要首先通过nn_socket调用创建socket,这里模仿了POSIX接口,
函数返回一个文件描述符。因此直接通过判断返回值是否大于0,判断是否创建成功。注意第二个参数为协议,
在协议相关头文件中会定义对应的宏。然后所有操作都将基于这个文件描述符。
和berkeley sockets一样,server需要bind一个端口,nanomsg需要bind一个url。目前nanomsg支持的格式有:
* 进程内通信(inproc):url格式为inproc://test
* 进程间同in想(ipc):url格式为ipc:///tmp/test.ipc
* tcp通信:url格式为tcp://*:5555

github上源码貌似已经支持websocket了。

nanomsg的错误和UNIX相同,失败之后会设置errno,可以通过nn_strerror获取对应的错误文本。

bind完了之后,就可以通过nn_send函数向socket发送消息了。这个函数参数和berkeley sockets api接口类似。
这里直接获取当前时间,然后发出给所有订阅者。

客户端

 代码如下 复制代码
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

#include &lt;nanomsg/nn.h&gt;
#include &lt;nanomsg/pubsub.h&gt;

int main(int argc, char **argv)
{
    if(argc != 3) {
        fprintf(stderr, "usage: %s NAME BIND_URLn", argv[0]);
        exit(-1);
    }
    const char *name = argv[1];
    const char *url = argv[2];

    int sock = nn_socket (AF_SP, NN_SUB);
    if(sock &lt; 0) {
        fprintf(stderr, "fail to create socket: %sn", nn_strerror(errno));
        exit(-1);
    }
    if(nn_setsockopt (sock, NN_SUB, NN_SUB_SUBSCRIBE, "", 0) &lt; 0) {
        fprintf(stderr, "fail to set sorket opts: %sn", nn_strerror(errno));
        exit(-1);
    }

    if (nn_connect(sock, url) &lt; 0) {
        fprintf(stderr, "fail to connect to %s : %sn", url, nn_strerror(errno));
        exit(-1);
    }


    while ( 1 ) {
        char *buf = NULL;
        int bytes = nn_recv (sock, &amp;buf, NN_MSG, 0);
        printf ("CLIENT (%s): RECEIVED %sn", name, buf);
        nn_freemsg (buf);
    }

    nn_shutdown(sock, 0);

    return 0;
}




客户端初始化和服务端差不多,在连接服务端之前,需要通过nn_setsockopt将当前socket设置成消息订阅者。
然后通过nn_connect连接发布者,参数和服务端bind的差不多,也是一个socket、一个url。
这里的url要和服务端bind的url相同。之后就是一个死循环不停的接收发布者的消息。

测试

首先是编译,和普通c程序相同,只是增加链接nanomsg。

gcc -o pubserver pubserver.c -lnanomsg
gcc -o pubclient pubclient.c -lnanomsg


为了方便测试,写了一个简单的shell脚本:
   

 代码如下 复制代码
#!/bin/bash

BASE="$( cd "$( dirname "$0" )" &amp;&amp; pwd )"
PUB=$BASE/pubserver
SUB=$BASE/pubclient

URL="tcp://127.0.0.1:1234"

echo "start pubserver to bind tcp: $URL"

$PUB tcp://127.0.0.1:1234 &amp;

echo "start to start pubclient"
for((i = 0; i &lt; 10; i++))
    do
    echo "start client$i"
    $SUB client$i $URL &amp;
    sleep 1
done

sleep 20
echo "kill all process and exit"

for pid in `jobs -p`
do
    echo "kill $pid"
    kill $pid
done

wait




脚本很简单,首先启动一个消息发布者,然后每秒启动一个消息接受者。等待20s之后,kill掉所有子进程。

脚本的输出:

 代码如下 复制代码
start pubserver to bind tcp: tcp://127.0.0.1:1234
start to start pubclient
start client0
SERVER: PUBLISHING DATE Tue Feb 17 15:12:11 2015
start client1
SERVER: PUBLISHING DATE Tue Feb 17 15:12:12 2015
CLIENT (client0): RECEIVED Tue Feb 17 15:12:12 2015
CLIENT (client1): RECEIVED Tue Feb 17 15:12:12 2015
start client2
SERVER: PUBLISHING DATE Tue Feb 17 15:12:13 2015
CLIENT (client0): RECEIVED Tue Feb 17 15:12:13 2015
CLIENT (client1): RECEIVED Tue Feb 17 15:12:13 2015
CLIENT (client2): RECEIVED Tue Feb 17 15:12:13 2015
start client3
SERVER: PUBLISHING DATE Tue Feb 17 15:12:14 2015
CLIENT (client0): RECEIVED Tue Feb 17 15:12:14 2015
CLIENT (client1): RECEIVED Tue Feb 17 15:12:14 2015
CLIENT (client2): RECEIVED Tue Feb 17 15:12:14 2015
...
SERVER: PUBLISHING DATE Tue Feb 17 15:12:41 2015
CLIENT (client0): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client1): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client2): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client3): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client4): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client5): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client6): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client7): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client8): RECEIVED Tue Feb 17 15:12:41 2015
CLIENT (client9): RECEIVED Tue Feb 17 15:12:41 2015
kill all process and exit




可以看见每次启动一个新的订阅者,每个订阅者都能够收到发布者发布的当前时间。


nanomsg实验——survey

survey模式是由server发出询问,client针对请求回复响应的一种模式。这种模式在分布式系统中非常有用,
可以用来做服务发现、分布式事物等分布式询问。
客户端

客户端实现比较方便,除了基础调用(创建socket、连接url)之外,就是先接收服务端询问
(例子中比较简单,服务端询问是固定的,所以没有对内容进行检查)针对询问发送响应
(例子中是发送服务端当前时间)

 代码如下 复制代码
#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;
#include &lt;cstring&gt;
#include &lt;ctime&gt;
#include &lt;nanomsg/nn.h&gt;
#include &lt;nanomsg/survey.h&gt;

using namespace std;

int main(int argc, const char **argv) {
    if(argc != 3) {
        fprintf(stderr, "usage: %s NAME URLn", argv[0]);
        exit(-1);
    }
    const char *name = argv[1];
    const char *url = argv[2];

    int sock = nn_socket(AF_SP, NN_RESPONDENT);
    if(sock &lt; 0){
        fprintf(stderr, "nn_socket fail: %sn", nn_strerror(errno));
        exit(-1);
    }
    if(nn_connect(sock, url) &lt; 0) {
        fprintf(stderr, "nn_connect fail: %sn", nn_strerror(errno));
        exit(-1);
    }

    while(1){
        char *buf = NULL;
        int bytes = nn_recv (sock, &amp;buf, NN_MSG, 0);

        if(bytes &gt; 0) {
            printf ("CLIENT (%s): RECEIVED "%s" SURVEY REQUESTn", name, buf);
            nn_freemsg (buf);

            char sendBuffer[128];
            time_t rawtime;
            struct tm * timeinfo;

            time (&amp;rawtime);
            timeinfo = localtime (&amp;rawtime);
            char *timeText = asctime (timeinfo);
            int textLen = strlen(timeText);
            timeText[textLen - 1] = '';
            sprintf(sendBuffer, "[ %s ] %s", name, timeText);
            int sendSize = strlen(sendBuffer) + 1;
            int actualSendSize = nn_send(sock, sendBuffer, sendSize, 0);

            if(actualSendSize != sendSize) {
                fprintf(stderr, "nn_send fail, expect length %d, actual length %dn", sendSize, actualSendSize);
                continue;
            }
        }
    }

    nn_shutdown(sock, 0);

    return 0;
}




这里收到消息后,就简单的打印,然后将响应数据写会给服务端。

服务端

服务端有个问题,之前搜索了几个例子都不太正常。经过尝试和简单查看代码之后发现,通过nanomsg基础api,
无法获取当前有多少客户端。但是,如果当前所有连接的客户端的响应都已经收到,再次调用nn_recv之后,
会直接返回-1,表示读取失败,同时errno(通过errno函数获取)被设置为EFSM,表示当前状态机状态不正确。

 代码如下 复制代码
#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;
#include &lt;cstring&gt;
#include &lt;unistd.h&gt;
#include &lt;nanomsg/nn.h&gt;
#include &lt;nanomsg/survey.h&gt;

using namespace std;

const char *SURVEY_TYPE = "DATE";

int main(int argc, char** argv)
{

    if ( argc != 2 ) {
        fprintf(stderr, "usage: %s URLn", argv[0]);
        exit(-1);
    }
    const char *url = argv[1];
    int sock = nn_socket(AF_SP, NN_SURVEYOR);
    if(sock &lt; 0) {
        fprintf (stderr, "nn_socket failed: %sn", nn_strerror (errno));
        exit(-1);
    }

    if(nn_bind(sock, url) &lt; 0) {
        fprintf(stderr, "nn_bind fail: %sn", nn_strerror(errno));
        exit(-1);
    }

    while(1) {
        int sendSize = strlen(SURVEY_TYPE) + 1;
        int actualSendSize;
        printf ("SERVER: SENDING DATE SURVEY REQUESTn");
        if ((actualSendSize = nn_send(sock, SURVEY_TYPE, sendSize, 0)) != sendSize) {
            fprintf(stderr, "nn_send fail, expect length %d, actual length %dn", sendSize, actualSendSize);
            continue;
        }

        int count = 0;
        while(1) {
            char *buf = NULL;
            int bytes = nn_recv (sock, &amp;buf, NN_MSG, 0);
            if (bytes &lt; 0 &amp;&amp; nn_errno() == ETIMEDOUT) break;
            if (bytes &gt;= 0) {
                printf ("SERVER: RECEIVED "%s" SURVEY RESPONSEn", buf);
                ++count;
                nn_freemsg (buf);
            } else {
                fprintf(stderr, "nn_recv fail: %sn", nn_strerror(errno));
                break;
            }
        }
        printf("SERVER: current receive %d survey response.n", count);
        sleep(1);
    }

    nn_shutdown(sock, 0);

    return 0;

}




这里用了两个死循环,外层循环不停尝试向客户端发起询问。完成询问后,通过另外一个死循环读取所有的客户端响应,
当读取失败时退出循环。

之前找到的源码是直接判断错误是否ETIMEDOUT,经过打印会发现每次都没有超时,而是状态机错误:

 代码如下 复制代码
/*  If no survey is going on return EFSM error. */
if (nn_slow (!nn_surveyor_inprogress (surveyor)))
    return -EFSM;




测试

测试和前文差不多,先启动一个server,然后再一个个启动client:

 代码如下 复制代码
#!/bin/bash

BASE="$( cd "$( dirname "$0" )" &amp;&amp; pwd )"
SERVER=$BASE/surveyserver
CLIENT=$BASE/surveyclient

URL="tcp://127.0.0.1:1234"

echo "start surveyserver to bind tcp: $URL"
$SERVER tcp://127.0.0.1:1234 &amp;

echo "start to start surveyclient"
for((i = 0; i &lt; 10; i++))
do
    echo "start client$i"
    $CLIENT client$i $URL &amp;
    sleep 1
done

sleep 20
echo "kill all process and exit"

for pid in `jobs -p`
do
    echo "kill $pid"
    kill $pid
done

wait




输出为:

 代码如下 复制代码
start surveyserver to bind tcp: tcp://127.0.0.1:1234
start to start surveyclient
start client0
SERVER: SENDING DATE SURVEY REQUEST
start client1
nn_recv fail: Operation cannot be performed in this state
SERVER: current receive 0 survey response.
start client2
SERVER: SENDING DATE SURVEY REQUEST
CLIENT (client0): RECEIVED "DATE" SURVEY REQUEST
SERVER: RECEIVED "[ client0 ] Tue Feb 17 23:32:43 2015" SURVEY RESPONSE
CLIENT (client1): RECEIVED "DATE" SURVEY REQUEST
SERVER: RECEIVED "[ client1 ] Tue Feb 17 23:32:43 2015" SURVEY RESPONSE
nn_recv fail: Operation cannot be performed in this state
SERVER: current receive 2 survey response.
start client3
SERVER: SENDING DATE SURVEY REQUEST
CLIENT (client0): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client1): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client2): RECEIVED "DATE" SURVEY REQUEST
...
SERVER: SENDING DATE SURVEY REQUEST
CLIENT (client0): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client1): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client2): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client3): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client4): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client5): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client6): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client7): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client9): RECEIVED "DATE" SURVEY REQUEST
CLIENT (client8): RECEIVED "DATE" SURVEY REQUEST
SERVER: RECEIVED "[ client0 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client1 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client2 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client3 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client4 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client5 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client6 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client7 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client9 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
SERVER: RECEIVED "[ client8 ] Tue Feb 17 23:33:09 2015" SURVEY RESPONSE
nn_recv fail: Operation cannot be performed in this state
SERVER: current receive 10 survey response.




从输出可以看见,每次最后一个接收完成之后,都会有一个“Operation cannot be performed in this state”
错误,也就是EFSM错误。

CVS 太古老已经没有多少人用了,很多人都转到了 Git 上,现在我们来介绍一下如何将项目从 CVS 上转到 Git 并和 Github 上 Fork 的项目合并的步骤

在捣鼓我的 Gregarius 时,发现无法读取 HTTPS 的 RSS , 追查发现是他所使用的 HTTP 客户端类 Snoopy 的原因。 想升级新版 Snoopy 却发现原作者已经几年都不更新了, Github 上倒是有人弄了几个镜像, 其中 hurrycaner 的这个 还对 README 进行了一些改进。 但所有镜像都没有 SourceForge 上的修改历史。

所以,我想作的是,基于 hurrycaner 的镜像进行 Fork, 但是要把 SourceForge 上的修改历史也弄进来。

CVS –> Git

现在应该没有人用 CVS 了把,SourceForge 也支持 Git 了, 但上面有些古老项目依然只有 CVS 。

把 CVS 转换成 Git 的工具还是有一些的,但从 一些讨论看来 似乎都做不到完美。 也难怪,CVS 的存储格式实在是有些奇怪, 代码、修改记录、修改注释都堆在一个文件中,解析起来肯定头疼。

由于害怕 cvs2git 会像 svn2git 那样转换时把作者缀上 UUID, 我先试了试 parsecvs , 但这货连使用说明都没有,放弃了。 然后用的是 StackOverflow 上最后一个人推荐的 crap 。 和上面的一样,都是简单 make 一下就有可执行文件用, 但比上面的帮助全,还有一个非常简单的例子。

这就可以开始了,先把 SourceForge 上的仓库下载下来:

$ mkdir Snoopy.cvs
 
$ rsync -av rsync://snoopy.cvs.sourceforge.net/cvsroot/snoopy/ Snoopy.cvs
receiving incremental file list
./
CVSROOT/
CVSROOT/.#checkoutlist
CVSROOT/.#commitinfo
CVSROOT/.#config
CVSROOT/.#cvswrappers
CVSROOT/.#editinfo
CVSROOT/.#loginfo
CVSROOT/.#modules
CVSROOT/.#notify
CVSROOT/.#rcsinfo
CVSROOT/.#taginfo
CVSROOT/.#verifymsg
CVSROOT/checkoutlist
CVSROOT/checkoutlist,v
CVSROOT/commitinfo
CVSROOT/commitinfo,v
CVSROOT/config
CVSROOT/config,v
CVSROOT/cvswrappers
CVSROOT/cvswrappers,v
CVSROOT/editinfo
CVSROOT/editinfo,v
CVSROOT/history
CVSROOT/loginfo
CVSROOT/loginfo,v
CVSROOT/modules
CVSROOT/modules,v
CVSROOT/notify
CVSROOT/notify,v
CVSROOT/passwd
CVSROOT/rcsinfo
CVSROOT/rcsinfo,v
CVSROOT/readers
CVSROOT/taginfo
CVSROOT/taginfo,v
CVSROOT/val-tags
CVSROOT/verifymsg
CVSROOT/verifymsg,v
CVSROOT/writers
CVSROOT/Emptydir/
Snoopy/
Snoopy/AUTHORS,v
Snoopy/COPYING.lib,v
Snoopy/ChangeLog,v
Snoopy/FAQ,v
Snoopy/INSTALL,v
Snoopy/Makefile.am,v
Snoopy/NEWS,v
Snoopy/README,v
Snoopy/Snoopy.class.php,v
Snoopy/TODO,v
Snoopy/autogen.sh,v
Snoopy/configure.in,v
Snoopy/Attic/
Snoopy/Attic/.cvsignore,v
Snoopy/Attic/COPYING,v
Snoopy/Attic/Snoopy.class.inc,v
 
sent 1,066 bytes  received 229,013 bytes  17,042.89 bytes/sec
total size is 225,573  speedup is 0.98

注意这和下载 CVS 代码是不一样的,这里下载的是 CVSROOT,仓库的原始码。

然后初始化一个 Git 仓库目录,用 crap 开始转换:

$ mkdir Snoopy.git
$ cd Snoopy.git
 
$ git init
 
$ ../crap/crap-clone /home/fwolf/dev/Snoopy.cvs Snoopy
Valid-requests Root Valid-responses valid-requests Repository Directory Max-dotdot Static-directory Sticky Entry Kopt Checkin-time Modified Is-modified Empty-conflicts UseUnchanged Unchanged Notify Questionable Argument Argumentx Global_option Gzip-stream wrapper-sendme-rcsOptions Set Gssapi-authenticate expand-modules ci co update diff log rlog add remove update-patches gzip-file-contents status rdiff tag rtag import admin export history release watch-on watch-off watch-add watch-remove watchers editors init annotate rannotate noop version
*********** CYCLE **********
Changeset  andrei
*** empty log message ***
 
    INSTALL:1.1
    Makefile.am:1.1
    NEWS:1.1
    autogen.sh:1.1
    configure.in:1.1
    .cvsignore:1.1
Deferring:
    autogen.sh:1.2
Tag 'Snoopy' placing on branch ''
Tag 'start' placing on branch 'Snoopy'
opening version cache failed: No such file or directory
1970-01-01 08:00:00 CST BRANCH
2000-02-03 23:40:59 CST COMMIT
2000-02-03 23:40:59 CST BRANCH Snoopy
2000-02-03 23:40:59 CST COMMIT
2000-02-03 23:40:59 CST COMMIT
2000-02-03 23:40:59 CST TAG start
2000-02-04 00:10:54 CST COMMIT
2000-02-04 00:10:54 CST COMMIT
2000-02-04 00:28:59 CST COMMIT
2000-02-22 23:44:57 CST COMMIT
2000-03-10 04:52:59 CST COMMIT
2000-03-10 04:54:47 CST COMMIT
2000-05-18 22:50:14 CST COMMIT
2000-05-18 23:36:34 CST COMMIT
2000-05-18 23:44:00 CST COMMIT
2000-06-30 02:37:25 CST COMMIT
2000-08-23 04:36:52 CST COMMIT
2000-09-14 04:52:04 CST COMMIT
2000-09-14 22:09:58 CST COMMIT
2000-09-15 21:11:11 CST COMMIT
2000-09-16 05:57:37 CST COMMIT
2000-09-27 03:34:38 CST COMMIT
2000-09-27 04:28:45 CST COMMIT
2000-10-09 21:13:52 CST COMMIT
2001-03-25 04:15:18 CST COMMIT
2001-07-07 05:24:11 CST COMMIT
2001-08-22 23:43:24 CST COMMIT
2001-11-21 04:23:02 CST COMMIT
2002-10-03 22:38:49 CST COMMIT
2002-10-03 22:55:06 CST COMMIT
2002-10-03 22:57:39 CST COMMIT
2002-10-10 04:25:50 CST COMMITMissed first time round: ChangeLog 1.11
Missed first time round: Snoopy.class.inc 1.21
 
2002-10-10 04:41:24 CST COMMITcvs checkout ChangeLog 1.14 - version is duplicate
cvs checkout Snoopy.class.inc 1.24 - version is duplicate
Missed first time round: ChangeLog 1.12
Missed first time round: Snoopy.class.inc 1.22
 
2002-10-10 04:51:57 CST COMMITcvs checkout ChangeLog 1.14 - version is duplicate
cvs checkout Snoopy.class.inc 1.24 - version is duplicate
Missed first time round: ChangeLog 1.13
Missed first time round: Snoopy.class.inc 1.23
 
2002-10-10 04:56:14 CST COMMIT
2003-03-12 22:40:55 CST COMMIT
2003-09-15 21:58:28 CST COMMIT
2003-10-22 03:18:39 CST COMMIT
2003-11-08 03:52:58 CST COMMIT
2003-12-24 03:34:35 CST COMMIT
2004-01-08 03:16:10 CST COMMIT
2004-07-25 02:23:27 CST COMMITMissed first time round: ChangeLog 1.19
Missed first time round: Snoopy.class.php 1.5
 
2004-07-25 02:34:28 CST COMMITcvs checkout ChangeLog 1.22 - version is duplicate
cvs checkout Snoopy.class.php 1.8 - version is duplicate
Missed first time round: ChangeLog 1.20
Missed first time round: Snoopy.class.php 1.6
 
2004-07-25 08:49:02 CST COMMIT
2004-07-25 10:42:48 CST COMMIT
2004-07-25 10:46:34 CST COMMIT
2004-07-25 10:46:59 CST COMMIT
2004-07-25 11:18:32 CST COMMIT
2004-10-16 13:14:11 CST COMMIT
2004-10-16 13:17:41 CST COMMIT
2004-10-16 13:44:51 CST COMMIT
2004-10-16 14:27:09 CST COMMIT
2004-10-16 14:28:30 CST COMMIT
2004-10-16 14:40:42 CST COMMIT
2004-10-17 00:33:58 CST COMMIT
2004-10-17 00:36:18 CST COMMIT
2004-10-18 13:12:55 CST COMMIT
2004-10-18 13:18:27 CST COMMIT
2004-10-18 13:19:04 CST COMMIT
2004-10-18 13:19:28 CST COMMIT
2004-10-18 13:19:51 CST COMMIT
2004-11-18 13:51:32 CST COMMIT
2004-11-18 13:52:28 CST COMMIT
2004-11-18 14:37:05 CST COMMIT
2005-02-03 12:43:26 CST COMMIT
2005-02-03 12:57:05 CST COMMIT
2005-10-23 10:08:40 CST COMMIT
2005-10-23 10:16:26 CST COMMIT
2005-10-24 00:30:34 CST COMMIT
2005-10-24 23:34:50 CST COMMIT
2005-10-24 23:44:12 CST COMMIT
2005-10-24 23:44:59 CST COMMIT
2005-10-24 23:46:10 CST COMMIT
2005-10-30 13:33:15 CST COMMIT
2005-10-30 13:45:09 CST COMMIT
2005-10-31 02:32:42 CST COMMIT
2005-10-31 02:51:35 CST COMMIT
2005-11-08 14:53:56 CST COMMIT
2005-11-08 15:01:47 CST COMMIT
2008-10-22 23:30:41 CST COMMIT
2008-10-22 23:53:14 CST COMMIT
2008-11-09 05:09:09 CST COMMIT
Emitted 79 commits (= total 79).
Exact     2 +     1 =     3 branches + tags.
Fixup     0 +     0 =     0 branches + tags.
Download 147 cvs versions in 84 transactions.
String cache: 141 items, 132/1024 buckets used, mean search 1.06383
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects:       5000
Total objects:          289 (         8 duplicates                  )
      blobs  :          134 (         7 duplicates         46 deltas of        133 attempts)
      trees  :           77 (         0 duplicates         70 deltas of         71 attempts)
      commits:           78 (         1 duplicates          0 deltas of          0 attempts)
      tags   :            0 (         0 duplicates          0 deltas of          0 attempts)
Total branches:           3 (         2 loads     )
      marks:           1024 (       220 unique    )
      atoms:             15
Memory total:          2294 KiB
       pools:          2098 KiB
     objects:           195 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize =   33554432
pack_report: core.packedGitLimit      =  268435456
pack_report: pack_used_ctr            =          7
pack_report: pack_mmap_calls          =          3
pack_report: pack_open_windows        =          1 /          1
pack_report: pack_mapped              =     350104 /     350104
---------------------------------------------------------------------

这样这个 Git 仓库就包含了已经转换过了的 CVS 历史记录, 如果看不到文件可以 reset 一下。

按说后续的操作理论上可以在这个仓库目录中操作,但为了更好的和 Fork 的项目合并, 我使用导出 Patch 的方法,后面再 am:

$ git log --pretty=oneline |wc -l
78
 
$ git format-patch -78

其实在这里,也可以在目标 repo 里面,通过添加 Snoopy.git 为 Git remote, 然后 merge remote 的方式进行,效果更好,还不用修改提交时间。

Fork 项目,移花接木

在 Github 上 Fork https://github.com/hurrycaner/snoopy , 得到 https://github.com/fwolf/snoopy , 但先不下载到本地,后面的操作方法和正常 Fork 项目是 不一样 的。

在本地再新建一个 Git 仓库,这个仓库是我们今后维护 Snoopy 的主仓库:

$ mkdir Snoopy
$ cd Snoopy
$ git init
$ git remote add origin git@github.com:fwolf/snoopy.git
$ touch .gitignore
$ git add .gitignore
$ git commit -a -m "Initial commit"
$ git push -f origin master

和新建项目的方法基本一样,不同点是我们的 origin 是 Fork 后的项目, 并且进行了 push -f 操作,覆盖掉了 hurrycaner 的所有提交。

接下来新建一个 sourceforge 分支,保留 SourceForge 上 CVS 代码的最终状态, 提交是通过 am 导入的, --committer-date-is-author-date 参数是将作者的时间作为提交时间, 也可以不要。Patch 0002 是空的,会导致 am 失败,所以删除掉:

$ git branch sourceforge
$ git checkout sourceforge
 
$ rm ../Snoopy.git/0002-Initial-check-in.patch
$ git am ../Snoopy.git/00* --committer-date-is-author-date
 
$ git checkout master
$ git merge sourceforge
$ git push

现在,master 分支上是我作的一个初始提交,加上 CVS 上导过来的提交内容, 相当于是 CVS 被完整的导入了 Git。

添加只有一个空 .gitignore 文件的初始提交是 Git 的一个习惯, 因为 Git 的初始提交可以视为是“不可以操作”的, 所以最好是空或者只包含最少内容。

接下来,我们要将 hurrycaner 所作的修改合并进来。 由于他是基于 Snoopy 1.2.4 代码修改的, 和我导入的最终代码差距不大,所以合并还比较顺利,只有几处冲突而已:

$ git branch hurrycaner
$ git checkout hurrycaner
$ git remote add upstream git@github.com:hurrycaner/snoopy.git
$ git fetch upstream
$ git merge upstream/master     # 手工解决冲突
 
$ git checkout master
$ git merge hurrycaner
$ git push

这就基本上完成了,保留了从 CVS 到 hurrycaner 的完整修改记录, 并且还能像正常 Fork 的项目那样继续工作。

修改记录看起来是这个样子的:

将项目从 CVS 转 Git 并和 Github 上 Fork 的项目合并的详细步骤

我已经向原项目作者推送 Pull Request 了。 hurrycaner 在 Github 上并不活跃,不知道能不能看到、会不会收啊。

尾声

Git 的使用是比较灵活的,我相信其他分布式 SCM 也能做到,没研究过,不对比。 话说回来,本文中的做法,是不是有点鸠占鹊巢的感觉?

[!--infotagslink--]

相关文章

  • php中去除文字内容中所有html代码

    PHP去除html、css样式、js格式的方法很多,但发现,它们基本都有一个弊端:空格往往清除不了 经过不断的研究,最终找到了一个理想的去除html包括空格css样式、js 的PHP函数。...2013-08-02
  • PHP传值到不同页面的三种常见方式及php和html之间传值问题

    在项目开发中经常见到不同页面之间传值在web工作中,本篇文章给大家列出了三种常见的方式。接触PHP也有几个月了,本文总结一下这段日子中,在编程过程里常用的3种不同页面传值方法,希望可以给大家参考。有什么意见也希望大...2015-11-24
  • jQuery实现切换页面过渡动画效果

    直接为大家介绍制作过程,希望大家可以喜欢。HTML结构该页面切换特效的HTML结构使用一个<main>元素来作为页面的包裹元素,div.cd-cover-layer用于制作页面切换时的遮罩层,div.cd-loading-bar是进行ajax加载时的loading进...2015-10-30
  • forum.php怎么打开?

    本文介绍了forum.php的打开方法,不会的同学可以参考一下。 虚拟空间,网页管理端, 会有一个默认访问页面(找一下,是这个意思,可能会有所不同),内容一般是 index.html index...2017-07-06
  • index.php怎么打开?如何打开index.php?

    index.php怎么打开?初学者可能不知道如何打开index.php,不会的同学可以参考一下本篇教程 打开编辑:右键->打开方式->经文本方式打开打开运行:首先你要有个支持运行PH...2017-07-06
  • 解决vue刷新页面以后丢失store的数据问题

    这篇文章主要介绍了解决vue刷新页面以后丢失store的数据问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-12
  • PHP页面转UTF-8中文编码乱码的解决办法

    对于乱码这个问题php开发者几乎都会有碰到过,我们下面主要是介绍了php文件乱码和页面乱码。PHP页面转UTF-8编码问题 1.在代码开始出加入一行: header("Content-Type: text/html;charset=utf-8"); 2.PHP文件编码问题...2015-10-21
  • 关于Visual Studio无法打开源文件"stdio.h"问题

    这篇文章主要介绍了关于Visual Studio无法打开源文件"stdio.h"问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-03
  • C# winform打开Excel文档的方法总结(必看篇)

    下面小编就为大家带来一篇C# winform打开Excel文档的方法总结(必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • IE6-IE9中tbody的innerHTML不能赋值的解决方法

    IE6-IE9中tbody的innerHTML不能赋值,重现代码如下 复制代码 代码如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>IE6-IE9中tbody的innerHTML不能复制bug</title> </head> <body style="height:3...2014-06-07
  • java后台实现js关闭本页面,父页面指定跳转或刷新操作

    这篇文章主要介绍了java后台实现js关闭本页面,父页面指定跳转或刷新操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-11-16
  • vue实现在进行增删改操作后刷新页面

    这篇文章主要介绍了vue实现在进行增删改操作后刷新页面,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-05
  • JavaScript 获取滚动条位置并将页面滑动到锚点

    这篇文章主要介绍了JavaScript 获取滚动条位置并将页面滑动到锚点的的相关资料,帮助大家更好的理解和学习使用JavaScript,感兴趣的朋友可以了解下...2021-02-09
  • Pycharm 跳转回之前所在页面的操作

    这篇文章主要介绍了Pycharm 跳转回之前所在页面的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-05
  • XML、HTML、CSS与JS的区别整理

    在BS中,xml,html,css和js我们都学过,起初分不清这四者的区别和联系,随着知识的增长,有了一些体会,下面通过本文给大家简单介绍 XML、HTML、CSS与JS的区别,需要的朋友参考下...2016-02-21
  • 解决vuex数据页面刷新后初始化操作

    这篇文章主要介绍了解决vuex数据页面刷新后初始化操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-26
  • Angular.js中下拉框实现渲染html的方法

    这篇文章主要给大家介绍了关于在Angular.js中下拉框实现渲染html的方法,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来跟着小编一起来学习学习吧。...2017-06-24
  • 微信小程序 教程之注册页面

    这篇文章主要介绍了微信小程序 注册页面的相关资料,需要的朋友可以参考下...2016-10-20
  • angularjs中ng-bind-html的用法总结

    这篇文章主要介绍了angularjs中ng-bind-html的用法总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2017-05-27
  • PHP正则表达式过滤html标签属性(DEMO)

    这篇文章主要介绍了PHP正则表达式过滤html标签属性的相关内容,实用性非常,感兴趣的朋友参考下吧...2016-05-06