吴峰的博客

php与C++的socket通讯——内容体使用protobuf存储

首先了解需求的前提:PHPC++进行socket通讯,用C++作为服务器端,php作为客户端进行,消息包使用protobuf存储。

socket通讯是基于协议的,因此,只要双方协议一致就行。

关于协议的选择:我看过网上大部分协议都是在应用层的协议,选用这样的协议很方便,基本上就是字符串传过来,传过去

本次研究的协议算是当今国际化的一个标准做法:length+flag+body(长度+类型+内容)的方式。

(1)先安装php-protobuf来生成对应的proto.php文件:

首相安装protobuf,安装的版本是:2.6.1

wget https://github.com/google/protobuf/releases/download/v2.6.1/protobuf-2.6.1.tar.gz
tar -zxvf protobuf-2.6.1.tar.gz
cd protobuf-2.6.1/
./configure
make
make check
sudo make install

安装完可以查看版本来检测是否安装成功:查看protobuf版本

protoc --version

然后安装开始安装: php-protobuf,版本为:allegro/php-protobuf

下面介绍centos(CentOS Linux release 7.0.1406 (Core))环境中下载,编译:

wget https://github.com/allegro/php-protobuf/archive/master.zip
unzip master.zip
cd php-protobuf-master

安装依赖

yum install php-devel

假如phpize不在默认目录,可以使用 which phpize查看,然后写全路径去执行phpize,我这里是默认在 /usr/bin目录,可以直接执行,执行之后可以看到以下输出

[root@VM_18_199_centos php-protobuf-master]# phpize
Configuring for:
PHP Api Version:         20100412
Zend Module Api No:      20100525
Zend Extension Api No:   220100525

php-config的目录也使用which php-config这个命令去查看路径,这一步需要安装gcc

yum -y install gcc-c++

安装完gcc之后则开始正式安装pb

./configure
make
make install

最后会看到类似这样的安装提示,则说明安装成功。

[root@VM_18_199_centos php-protobuf-master]# make install
Installing shared extensions:     /usr/lib64/php/modules/

现已成功把pb编译至/usr/lib64/php/modules/protobuf.so

开启php的pb扩展

找到php.in,加入这行代码:

extension=/usr/lib64/php/modules/protobuf.so

重启nginx

/usr/sbin/nginx -s reload

//重启php-fpm
kill -SIGUSR2 `cat /run/php-fpm/php-fpm.pid`
//我一般使用killall 比较干脆利落,然后在启动
killall php-fpm
/use/sbin/php-fpm

假如是Apache,则重启Apache

//centos 7.0
systemctl restart httpd
//centos 6.5
service httpd restart

编译proto文件

新建一个简单的proto文件:

vim test.proto

输入以下内容:

message PhoneNumber {
    required string number = 1;
    required int32 type = 2;
  }

message Person {
      required string name = 1;
      required int32 id = 2;
      optional string email = 3;
      repeated PhoneNumber phone = 4;
      optional double money = 5;
}

message AddressBook {
  repeated Person person = 1;
}

编译。注意,这里需要引用到protoc-php.php这个文件,注意路径。

php ./php-protobuf-master/protoc-php.php  test.proto

编译成功后会在当前目录生成一个pb_proto_test.php文件,内容如下:

 /**
 * Auto generated from test.proto at 2015-10-22 23:19:46
 */

/**
 * PhoneNumber message
 */
class PhoneNumber extends \ProtobufMessage
{
    /* Field index constants */
    const NUMBER = 1;
    const TYPE = 2;

    /* @var array Field descriptors */
    protected static $fields = array(
        self::NUMBER => array(
            'name' => 'number',
            'required' => true,
            'type' => 7,
        ),
        self::TYPE => array(
            'name' => 'type',
            'required' => true,
            'type' => 5,
        ),
    );

    /**
     * Constructs new message container and clears its internal state
     *
     * @return null
     */
    public function __construct()
    {
        $this->reset();
    }

    .......

}

这里会继承ProtobufMessage类,它是在protobuf.so中自动加载的。
写一个测试类来测试一下:


require_once 'pb_proto_test.php';

$foo = new Person();
$foo->setName('hellojammy');
$foo->setId(2);
$foo->setEmail('helloxxx@foxmail.com');
$foo->setMoney(1988894.995);

$phone_num = new PhoneNumber();
$phone_num->setNumber('1351010xxxx');
$phone_num->setType(3);

$foo->appendPhone($phone_num);
//$foo->appendPhone(2);
$packed = $foo->serializeToString();
//echo $packed;exit;
#$foo->clear();
echo "-----------src------------\n";
echo $foo->getName() ."\n";
echo $foo->getPhone()[0]->getNumber() ."\n";
$foo->dump();
echo "------------------------\n\n\n";


try {
      $p = new Person();
      $p->parseFromString($packed);
      echo "------------parsed-------\n";
      echo $p->getName() ."\n";
      echo $p->getEmail() ."\n";
      echo $p->getMoney() ."\n";
      echo $p->getId() . "\n";
      echo $p->getPhone()[0]->getNumber() ."\n";

      //$p->dump();
      echo "------------------------\n";
      //print_r($xiao);
      } catch (Exception $ex) {
      die('Upss.. there is a bug in this example');
}

运行

php text.php

可以看到输出:

-----------src------------
hellojammy
1351010xxxx
Person {
  1: name => 'hellojammy'
  2: id => 2
  3: email => 'helloxxx@foxmail.com'
  4: phone(1) =>
    [0] =>
      PhoneNumber {
        1: number => '1351010xxxx'
        2: type => 3
      }
  5: money => 1988894.995
}
------------------------


------------parsed-------
hellojammy
helloxxx@foxmail.com
1988894.995
2
1351010xxxx
------------------------

到这里基本上prootobuf的拓展安装时是完成了。下面开始和C++的服务端通讯了。


(2)然后使用上面讲到的方法生成消息包内容,通过socket协议进行通讯将生成的消息包发出并接收消息包:

[php] 

$data[]='testzouhao';  

$data[]='a';  

$gameSocket=new GameSocket();  

$gameSocket->code=11;  

$gameSocket->write($data);  

//封装好二进制协议包

class Byte{  

    //长度  

    private $length=0;  

    private $byte='';  

    //操作码  

    private $code;  

    public function setBytePrev($content){  

        $this->byte=$content.$this->byte;  

    }  

    public function getByte(){  

        return $this->byte;  

    }  

    public function getLength(){  

        return $this->length;  

    }  

    public function writeChar($string){  

        $this->length+=strlen($string);  

        $str=array_map('ord',str_split($string));  

        foreach($str as $vo){  

            $this->byte.=pack('c',$vo);  

        }  

        $this->byte.=pack('c','0');  

        $this->length++;  

    }  

    public function writeInt($str){  

        $this->length+=4;  

        $this->byte.=pack('L',$str);  

    }  

    public function writeShortInt($interge){  

        $this->length+=2;  

        $this->byte.=pack('v',$interge);  

    }  

}  

class GameSocket{  

    private $socket;  

    private $port=9991;  

    private $host='192.168.211.231';  

    private $byte;  

    private $code;  

    const CODE_LENGTH=2;  

    const FLAG_LENGTH=4;  

    public function __set($name,$value){  

        $this->$name=$value;  

    }  

    public function __construct($host='192.168.211.231',$port=9991){  

        $this->host=$host;  

        $this->port=$port;  

        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);  

        if(!$this->socket){  

            exit('创建socket失败');  

        }  

        $result = socket_connect($this->socket,$this->host,$this->port);  

        if(!$result){  

            exit('连接不上目标主机'.$this->host);  

        }  

        $this->byte=new Byte();  

    }  

    public function write($data){  

        if(is_string($data)||is_int($data)||is_float($data)){  

            $data[]=$data;  

        }  

        if(is_array($data)){  

            foreach($data as $vo){  

                $this->byte->writeShortInt(strlen($vo));  

                $this->byte->writeChar($vo);  

            }  

        }  

        $this->setPrev();  

        $this->send();  

    }  

    /* 

     *设置表头部分 

     *表头=length+code+flag 

     *length是总长度(4字节)  code操作标志(2字节)  flag暂时无用(4字节) 

     */  

    private function getHeader(){  

        $length=$this->byte->getLength();  

        $length=intval($length)+self::CODE_LENGTH+self::FLAG_LENGTH;  

        return pack('L',$length);  

    }  

    private function getCode(){  

        return pack('v',$this->code);  

    }  

    private function getFlag(){  

        return pack('L',24);  

    }  

      

    private function setPrev(){  

        $this->byte->setBytePrev($this->getHeader().$this->getCode().$this->getFlag());  

    }  

  

    private function send(){  

        $result=socket_write($this->socket,$this->byte->getByte());  

        if(!$result){  

            exit('发送信息失败');  

        }  

    }  

    public function __desctruct(){  

        socket_close($this->socket);  

    }  

}  

  




Tags:
PHP
评论 (1)
  • markus

    markus

    http://imrdsoacha.gov.co/silvitra-120mg-qrms

  • 说点什么吧... (取消回复)

    正在加载验证码......

    请先拖动验证码到相应位置

Copyright 吴峰的博客 © 2014-2016 管理员邮箱:phpwufeng@163.com   统计:   ICP备案:鲁ICP备16004939号-1