php与C++的socket通讯——内容体使用protobuf存储
首先了解需求的前提:PHP和C++进行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);
}
}
评论 (1)