概述
最近正在设计一些关于PHP与linux程序进行通信的工作,首先php和linux程序一般是不会运行在一个进程里,这样如果需要通信只能是跨进程实现。
这里可以使用两种方法,一种是用c语言分别实现通信接口,然后将其中一种编译成php扩展,让php调用,这类自由度比较高,但是有一定难度。还有一种就是直接使用php支持的ipc接口,与其他进程通信。
今天我们来研究一下php使用现有的IPC与其他进程通信的方法,这个方法在大多数时候都是足够满足需求的,下面我们来分几个步骤一一实现这个功能。
linux消息队列例程
发送例程
send.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
long type;
char name[20];
}Msg;
int main()
{
key_t key = ftok("/home",'6');
printf("key:%x\n",key);
int msgid = msgget(key,IPC_CREAT|O_WRONLY|0777);
if(msgid<0)
{
perror("msgget error!");
exit(-1);
}
Msg m;
m.type = 6;
memcpy(m.name, "hello!", 7);
msgsnd(msgid,&m,sizeof(m)-sizeof(m.type),0);
return 0;
}
非常简单,就是将一个20个字节大小的数据通过消息队列发送出去。
接收例程
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct{
long type;
char name[20];
}Msg;
int main()
{
key_t key = ftok("/home",'6');
printf("key:%x\n",key);
int msgid = msgget(key,O_RDONLY|IPC_CREAT|0777);
if(msgid<0)
{
perror("msgget error!");
exit(-1);
}
Msg rcv;
long type = 6;
msgrcv(msgid,&rcv,sizeof(rcv)-sizeof(type),type,0);
printf("rcv--name:%s\n",rcv.name);
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
和发送例程对应,接收数据并打印出来。
运行结果如下:
~/test/msg$ ./recv
key:36020001
rcv--name:hello!
PHP发送例程
先上代码send.php
<?php
$message_queue_key = ftok("/home",'6');
$message_queue = msg_get_queue($message_queue_key, 0777);
//向消息队列中写
msg_send($message_queue, 6, "Hello!", FALSE);
?>
非常简单,对于主要函数可以和c语言的一一对应,具体参数可以查看如下网站介绍
https://www.php.net/manual/en/function.ftok.php
fork对应fork,msg_get_queue对应msgget, msg_send对应msgsnd。
运行结果如下:
$ php ./send.php
$ ./recv
key:36020001
rcv--name:Hello!
PHP接收例程
<?php
$message_queue_key = ftok("/home",'6');
$message_queue = msg_get_queue($message_queue_key, 0777);
//从消息队列中读
msg_receive($message_queue, 6, $message_type, 1024, $message, FALSE);
print_r("php rcv--name:".$message."\n");
msg_remove_queue($message_queue);
?>
同样很简单,也可以与c语言的对应上。
fork对应fork,msg_get_queue对应msgget, msg_receive对应msgrcv,msg_remove_queue对应msgctl(msgid,IPC_RMID,NULL);删除消息队列。
运行结果如下:
$ ./send
key:36020001
$ php ./recv.php
php rcv--name:hello!
到此我们已经可以使用php与c语言的程序进行消息队列通信,但是有一点不足就是消息之传输了字符串。
结构体的支持
在c语言中消息往往是复合类型的结构体,那么我们如何与php传输结构体呢?如我们所知,php是弱类型变量,没有结构体概念年,类似的有数组和类,但是这个结构不能与c语言的结构体一一对应,好在php提供了pack和unpack函数。
pack — 将数据打包成二进制字符串
参考网站:https://www.php.net/manual/zh/function.pack.php
unpack — 从字串中的二进制串转化成指定的格式
目前已实现的格式如下:
代码 | 描述 |
---|---|
a | 以NUL字节填充字符串 |
A | 以SPACE(空格)填充字符串 |
h | 十六进制字符串,低位在前 |
H | 十六进制字符串,高位在前 |
c | 有符号字符 |
C | 无符号字符 |
s | 有符号短整型(16位,主机字节序) |
S | 无符号短整型(16位,主机字节序) |
n | 无符号短整型(16位,大端字节序) |
v | 无符号短整型(16位,小端字节序) |
i | 有符号整型(机器相关大小字节序) |
I | 无符号整型(机器相关大小字节序) |
l | 有符号长整型(32位,主机字节序) |
L | 无符号长整型(32位,主机字节序) |
N | 无符号长整型(32位,大端字节序) |
V | 无符号长整型(32位,小端字节序) |
q | 有符号长长整型(64位,主机字节序) |
Q | 无符号长长整型(64位,主机字节序) |
J | 无符号长长整型(64位,大端字节序) |
P | 无符号长长整型(64位,小端字节序) |
f | 单精度浮点型(机器相关大小) |
g | 单精度浮点型(机器相关大小,小端字节序) |
G | 单精度浮点型(机器相关大小,大端字节序) |
d | 双精度浮点型(机器相关大小) |
e | 双精度浮点型(机器相关大小,小端字节序) |
E | 双精度浮点型(机器相关大小,大端字节序) |
x | NUL字节 |
X | 回退已字节 |
Z | 以NUL字节填充字符串空白(PHP 5.5中新加入的) |
@ | NUL填充到绝对位置 |
例如:
typedef struct{
char name[20];
int age;
}Msg;
下面看一下PHP如何将他们转换在转回来的
//将PHP变量转成结构
$name = "13901234567";
$age = 33;
$returnstr = sprintf("%-20s",$name).pack("l",$age);
//将结构转成php变量
//对于字符型变量可以直接取得
$name = substr($returnstr,0,20);
$fuarray=unpack("nint",substr($returnstr,20,4));
//其中n是类型,与pack相同,int是保存的变量脚标
$age = $fuarray['int'];
//age = 33
对于char类型的字符可以直接使用sprintf转换,具体使用方法请看
https://www.w3school.com.cn/php/func_string_sprintf.asp
其中pack和unpack想看更好地例子可以到w3chool上去看看。
https://www.w3school.com.cn/php/func_misc_unpack.asp
在php转换结构体的时候千万要注意字节对齐问题。
好了,我们现在我们有了方法,写个例子测试一下,首先完成一下linux的程序
send.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
long type;
char name[20];
int age;
}Msg;
int main()
{
key_t key = ftok("/home",'6');
printf("key:%x\n",key);
int msgid = msgget(key,IPC_CREAT|O_WRONLY|0777);
if(msgid<0)
{
perror("msgget error!");
exit(-1);
}
Msg m;
m.type = 6;
memset(m.name, 0, 20);
memcpy(m.name, "hello!", 7);
m.age = 22;
msgsnd(msgid,&m,sizeof(m)-sizeof(m.type),0);
return 0;
}
recv.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct{
long type;
char name[20];
int age;
}Msg;
int main()
{
key_t key = ftok("/home",'6');
printf("key:%x\n",key);
int msgid = msgget(key,O_RDONLY|IPC_CREAT|0777);
if(msgid<0)
{
perror("msgget error!");
exit(-1);
}
Msg rcv;
long type = 6;
msgrcv(msgid,&rcv,sizeof(rcv)-sizeof(type),type,0);
printf("rcv--name:%s age:%d\n",rcv.name,rcv.age);
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
运行结果如下:
$ ./send
key:36020001
$ ./recv
key:36020001
rcv--name:hello! age:22
没有问题,下面我们要修改php代码,让php也能发送接收结构体。
php支持结构体发送
send.php
<?php
$message_queue_key = ftok("/home",'6');
$message_queue = msg_get_queue($message_queue_key, 0777);
$name = "Hello";
$age = 33;
$msg = sprintf("%-20s",$name).pack("l",$age);
msg_send($message_queue, 6, $msg, FALSE);
?>
使用linux程序接收的结果:
$ ./recv
key:36020001
rcv--name:Hello ! age:33
这里有一个问题,就是接收到的字符串很长,并且有“!”,这里说明一下,因为php使用的spritf进行的填充,默认填充的空格,所以字符串没有结束符“/0”,导致字符串很长,那么那个!是什么呢,就是那个年龄33。
php支持结构体接收
recv.php
<?php
$message_queue_key = ftok("/home",'6');
$message_queue = msg_get_queue($message_queue_key, 0777);
//从消息队列中读
msg_receive($message_queue, 6, $message_type, 30, $message, FALSE);
$name = substr($message,0,20);
$fuarray = unpack("lint",substr($message,20,4));
$age = $fuarray['int'];
print_r("php rcv--name:".$name." age:".$age."\n");
msg_remove_queue($message_queue);
?>
执行结果:
$ php ./recv.php
php rcv--name:hello! age:22
好了,基本就是这些了,这个只是最最基本的东西,剩下的还需要自己去完善。