用php-x写php扩展

https://yuerblog.cc/wp-content/uploads/%E9%9F%A9%E5%A4%A9%E5%B3%B0%E2%80%94%E2%80%94%E4%BD%BF%E7%94%A8C++%E5%BC%80%E5%8F%91PHP7%E6%89%A9%E5%B1%95.pdf

Zookeeper学习系列【一】 教会你Zookeeper的一些基础概念

https://segmentfault.com/a/1190000018927058

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

如何基于 PHP-X 快速开发一个 PHP 扩展

https://segmentfault.com/a/1190000011111074?utm_source=coffeephp.com

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。

php pcntl多进程

需要安装PHP扩展 pcntl posix

<?php
/**
 * Created by PhpStorm.
 * User: zhangheg
 * Date: 2019/4/14
 * Time: 18:06
 */
$a = 1;

$ppid = posix_getpid();//获取当前的进程号
echo $ppid.PHP_EOL;
//创建多个进程
for($i=0;$i<2;$i++){
    $pid = pcntl_fork(); //创建成功返回子进程ID
    if($pid<0){
        //父进程空间
        exit('创建失败了');
    }else if($pid>0){
        //父进程空间返回子进程ID
        $a = 2;

        $status = 0;//等待回收僵尸进程
        $pid = pcntl_wait($status); //结束的子进程信息,阻塞状态
        echo "字进程回收了id:$pid".PHP_EOL;
    }else{
        //返回为0子进程空间
        //子进程创建成功
        sleep(10);

    }
}

PHP 模拟开发 单进程阻塞网络服务器 -理解swoole实现

1 单进程阻塞网络服务器

一个连接-请求-阻塞-返回数据

原理 :创建-绑定-监听-接收请求-回复-逻辑处理-关闭

说明:

1、创建一个socket,绑定服务器端口(bind),监听端口(listen),在PHP中用stream_socket_server一个函数就能完成上面3个步骤

2、进入while循环,阻塞在accept操作上,等待客户端连接进入。此时程序会进入睡眠状态,直到有新的客户端发起connect到服务器,操作系统会唤醒此进程。accept函数返回客户端连接的socket

3、利用fread读取客户端socket当中的数据收到数据后服务器程序进行处理然后使用fwrite向客户端发送响应。长连接的服务会持续与客户端交互,而短连接服务一般收到响应就会close。

缺点:

1、一次只能处理一个连接,不支持多个连接同时处理

 

每个连接进入到我们的服务端的时候,单独创建一个进程/线程提供服务

<?php
//多个客户端发起请求,观察服务端状态
//ab -n 请求数 -c 并发数 -k 保持连接
 class Worker{
     //监听socket
     protected $socket = NULL;
     //连接事件回调
     public $onConnect = NULL;
     //接收消息事件回调
     public $onMessage = NULL;
     public function __construct($socket_address) {
        //监听地址+端口 相当于socket 绑定监听
         $this->socket = stream_socket_server($socket_address);
     }

     public function start() {
        while (true){

         $clientSocket = stream_socket_accept($this->socket); //阻塞监听

         if(!empty($clientSocket) && is_callable($this->onConnect)){
             //有客户端进来
             call_user_func($this->onConnect,$clientSocket); //连接建立成功触发
         }
         //从连接当中读取客户端内容
         $buffer = fread($clientSocket,65535); //缓冲区
         //正常读取到数据,触发消息接收事件,响应内容
         if(!empty($buffer) && is_callable($this->onRecive)){
             call_user_func($this->onRecive,$clientSocket,$buffer);
         }
        fclose($clientSocket);
        }

     }
 }



$worker = new Worker('tcp://0.0.0.0:9812');

 //事件
$worker->onConnect = function ($fd) {
        echo '连接事件触发'.(int)$fd.PHP_EOL;
};
$worker->onRecive = function ($conn, $message) {
    //事件回调当中写业务逻辑
    var_dump($conn,$message);
    //http 响应头
    $content = '响应http了';
    $http_resonse = "HTTP/1.1 200 OK\r\n";
    $http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
    $http_resonse .= "Connection: keep-alive\r\n"; //连接保持
    $http_resonse .= "Server: php socket server\r\n";
    $http_resonse .= "Content-length: ".strlen($content)."\r\n\r\n";
    $http_resonse .= $content;
    fwrite($conn, $http_resonse);

};

$worker->start();


线程、进程结构

[root@localhost ~]# pstree -ap | grep server.php
|-php,7900 server.php
| |-php,7901 server.php
| | `-php,7903 server.php
| | |-grep,8558 –color=auto server.php
| `-php,8548 server.php
| |-php,8549 server.php
| | `-php,8551 server.php

http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

进程与线程的一个简单解释

三、进程线程的区别

3.1进程

1、进程之间不共享任何状态

2、进程的调度由操作系统完成

3、每个进程都有自己独立的内存空间

4、进程间通讯主要是通过信号传递的方式来实现的,实现方式有多种,信号量、管道、事件等,任何一种方式的通讯效率都需要过内核,导致通讯效率比较低

5、由于是独立的内存空间,上下文切换的时候需要保存先调用栈的信息、cpu各寄存器的信息、虚拟内存、以及打开的相关句柄等信息,所以导致上下文进程间切换开销很大,通讯麻烦。

3.2线程

1、线程之间共享变量,解决了通讯麻烦的问题对于变量的访问需要锁

2、一个进程可以拥有多个线程,但是其中每个线程会共享父进程像操作系统申请资源,这个包括虚拟内存、文件等,由于是共享资源,所以创建线程所需要的系统资源占用比进程小很多,相应的可创建的线程数量也变得相对多很多。

3、另外在调度方面也是由于内存是共享的,所以上下文切换的时候需要保存的东西就像对少一些,这样一来上下文的切换也变得高效。

swoole 自定义协议解决粘包

字节序,不通的设备系统中按照特定的方式读取数据

 

思路:客户端 定义包头 前几个字节 描述包体长度 pack。服务端按此规则upack

<?php
$client = new swoole_client(SWOOLE_SOCK_TCP);
$client->connect('127.0.0.1',9800);
//一次性发送多条,会粘一起
//for($i=1;$i<=10;$i++){
////解决办法,约定一个分隔符,弊端传输数据中不能包含分隔符
//$client->send("123456\r\n");
//}
//一次发送大量数据,拆分小数据
$body = json_encode(str_repeat('a',1*1024*1024));

$data = pack("N",strlen($body)).$body;
$client->send($data);
//echo $client->recv();
<?php
/**
 * Created by PhpStorm.
 * User: zhangheg
 * Date: 2019/3/23
 * Time: 17:45
 */
//创建Server对象,监听 0.0.0.0:9501端口
$serv = new swoole_server("0.0.0.0", 9800);

$serv->set([
    'worker_num' => 1, //设置进程
    //'heartbeat_check_interval'=>3, //心跳间隔时间
    //'heartbeat_idle_time' => 15, //允许空闲时间 心跳的两倍,容错,允许一次丢包
    'open_length_check' => true,
    'package_max_length' => 1024*1024*2, //允许最大
    'package_length_type' => 'N', //设置包头的长度
    'package_length_offset' => 0,
    'package_body_offset' => 4, //包体从第几个字节开始计算
]);


//监听连接进入事件,有客户端连接进来的时候会触发
$serv->on('connect',array(new A(),'connect'));


//监听数据接收事件,server接收到客户端的数据后,worker进程内触发该回调
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    //var_dump("消息过来:".strlen($data));
    //解包 并截取数据包 截取的长度就是包头长度
   $info = unpack("N",$data);
    var_dump('长度:',$info);
    var_dump(substr($data,4));
    //$serv->send($fd, "服务器给你发送消息了: ".$data);
});
}); //监听连接关闭事件,客服端关闭,或者服务器主动关闭 $serv->on('close', function ($serv, $fd) { echo "编号为{$fd}的客户端已经关闭.".PHP_EOL; }); //启动服务器 $serv->start(); class A{ public function connect($serv, $fd){ var_dump('classa连接'); echo "有新的客户端连接,连接标识为$fd" . PHP_EOL; } }

注意:如果没有按照固定的格式打包方式打包 或者数据包过大 都会报

[2019-04-07 12:56:29 #7631.0] WARNING swProtocol_recv_check_length: package is too big, [2019-04-07 12:56:29 #7631.0] WARNING swProtocol_recv_check_length: package is too big,

每个socket被创建后都会产生两个 缓冲区,输入、输出缓冲区。一旦写入到缓冲区函数就会返回。不管是否发送到目标机器,也不管何时发送,这些都是tcp协议负责的

注意:回复机制。不要send后立即close

 

tcp 心跳,定时器

心跳解决问题:客户端到服务端 有设备休眠,比如路由器,交换机等导致连接断开

<?php
/**
 * Created by PhpStorm.
 * User: zhangheg
 * Date: 2019/3/23
 * Time: 14:52
 */

$client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$client->on("connect", function(swoole_client $cli) {
    $cli->send("GET / HTTP/1.1\r\n\r\n");
});
//必须注册所有时间
$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 "超过15秒没发送断开了 Connection close\n";
});
$client->connect('127.0.0.1', 9501) || exit('连接失败');

//定时器保持长链接
swoole_timer_tick(9000,function () use ($client){
   $client->send(1);
});
echo "写日志".PHP_EOL;
echo "请求api".PHP_EOL;

<?php
/**
 * Created by PhpStorm.
 * User: zhangheg
 * Date: 2019/3/23
 * Time: 14:52
 */

$client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$client->on("connect", function(swoole_client $cli) {
    $cli->send("GET / HTTP/1.1\r\n\r\n");
});
//必须注册所有时间
$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 "超过15秒没发送断开了 Connection close\n";
});
$client->connect('127.0.0.1', 9501) || exit('连接失败');

//定时器保持长链接
swoole_timer_tick(9000,function () use ($client){
   $client->send(1);
});
echo "写日志".PHP_EOL;
echo "请求api".PHP_EOL;

TCP 服务 客户端与服务端

<?php
/**
 * Created by PhpStorm.
 * User: zhangheg
 * Date: 2019/3/23
 * Time: 14:52
 */

// 创建一个同步非阻塞客户端tcp socket
// 第一个参数是表示socket的类型,有下面四种类型选择,这里选则tcp socket就好
/*
SWOOLE_SOCK_TCP 创建tcp socket
SWOOLE_SOCK_TCP6 创建tcp ipv6 socket
SWOOLE_SOCK_UDP 创建udp socket
SWOOLE_SOCK_UDP6 创建udp ipv6 socket
*/
// 第二个参数是同步还是异步
/*
SWOOLE_SOCK_SYNC 同步客户端
SWOOLE_SOCK_ASYNC 异步客户端
*/

$client = new swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);
// 随后建立连接,连接失败直接退出并打印错误码
$client->connect('127.0.0.1', 9888) || exit("connect failed. Error: {$client->errCode}\n");
// 向服务端发送数据
$client->send("我要连接服务器");
// 从服务端接收数据
$response = $client->recv();
// 输出接受到的数据
echo $response . PHP_EOL;
// 关闭连接
$client->close();
<?php
/**
 * Created by PhpStorm.
 * User: zhangheg
 * Date: 2019/3/23
 * Time: 14:51
 */

//创建Server对象,监听 0.0.0.0:9888端口
$serv = new swoole_server("0.0.0.0", 9888);

$serv->set([
 'worker_num' => 2, //设置进程
]);


//监听连接进入事件,有客户端连接进来的时候会触发
$serv->on('connect',array(new A(),'connect'));


//监听数据接收事件,server接收到客户端的数据后,worker进程内触发该回调
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
 $serv->send($fd, "服务器给你发送消息了: ".$data);
});

//监听连接关闭事件,客服端关闭,或者服务器主动关闭
$serv->on('close', function ($serv, $fd) {
 echo "编号为{$fd}的客户端已经关闭.".PHP_EOL;
});
//启动服务器
$serv->start();

class A{
 

 public function connect($serv, $fd){
 var_dump('classa连接');
 echo "有新的客户端连接,连接标识为$fd" . PHP_EOL;
 }
}

centos php7 swoole编译安装

1、安装编译所需扩展

yum -y  install  gcc  gcc-c++ libxml2-devel m4 autoconf pcre-devel make cmake bison openssl openssl-devel wget

2、下载

Wget  http://cn2.php.net/distributions/php-7.2.15.tar.gz

3、解压

tar -zxf

4、configure 生成安装文件

在安装包下面一般有个configure, 是用来生成 Makefile,为下一步的编译做准备,你可以通过在 configure 后加上参数来对安装进行控制,具体可以通过configure  –help 查看相应的命令,这里只指定了php目录跟配置文件目录,开放了其中一部分扩展

./configure --prefix=/usr/local/php
--prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc
-with-libxml-dir=/usr
--with-mhash --with-openssl
--with-mysqli=shared,mysqlnd --with-pdo-mysql=shared,mysqlnd
--with-zlib
--enable-zip
--enable-inline-optimization
--disable-debug
--disable-rpath
--enable-shared
--enable-xml
--enable-bcmath
--enable-shmop
--enable-sysvsem
--enable-mbregex
--enable-mbstring
--enable-pcntl
--enable-sockets
--without-pear
--with-gettext
--enable-session

 

5、 make && make install 安装

6、CentOS下将php和mysql命令加入到环境变量中

在centos7.2当中就可以使用下面的方式进行添加

Vim /etc/profile文件

添加一行

pathmunge  /usr/local/php/bin

然后执行source /etc/profile 让当前的配置生效

7、最后配置文件拷贝到指定的目录

php -i | grep Configuration 查询配置文件目录

将源码当中的配置文件指定到相应的目录

cp php.ini-development  /usr/local/php/etc/php.ini

Swoole安装方式跟php安装方式是一样的,下载解压、编译

wget  https://pecl.php.net/get/swoole-4.2.13.tgz

tar -zxf

phpize

./configure

make && make install

php.ini添加

extension=swoole.so