概述
最近正在設計一些關於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
好了,基本就是這些了,這個只是最最基本的東西,剩下的還需要自己去完善。