php與linux程序的消息隊列通信

概述

最近正在設計一些關於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 — 從字串中的二進制串轉化成指定的格式

目前已實現的格式如下:

pack() 格式字符
代碼 描述
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

好了,基本就是這些了,這個只是最最基本的東西,剩下的還需要自己去完善。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章