python與c-跨語言級別的進程間通信

分類:IT技術 時間:2017-10-04

今天發文比較多,哈,實在是覺得知識就該及時沈澱下來,時間長了難免記憶會模糊。
OK,直接切入正題,之前http://t.vimer.cn上提過正在開發的fuload壓力測試框架,由於是想拿python做膠水語言,所以不可避免的涉及到了進程間通信的問題。
簡單來說就是,一個python寫的主進程與多個c寫的處理進程通信的問題。主進程啟動之後,會啟動多個c的處理進程,主進程會對處理進程發送數據,並控制處理進程。
這種情況在server的編寫中比較常見,為了解耦一般會將接受數據的進程與處理進程分開,在c中的實現一般是主進程先fork出子進程,然後在子進程中調用exec將自身替換為處理進程,進程id不變。這樣主進程即可拿到所有的子進程id進行統一管理。
python當然也可以通過這種方式來實現,fork+execv即可完美重現,但是這可是無所不能的python呀,是否有更好的方式呢?
有的!python2.4之後引入了subprocess模塊,通過它,我們將不再需要繁瑣的調用fork,execv等,其主要函數如下:

以下是代碼片段:
class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
 
#args需要是一個字符串,或者包含程序參數的列表。要執行的程序一般就是這個列表的第一項,或者是字符串本身。但是也可以用executable參數來明確指出。當executable參數不為空時,args裏的第一項仍被認為是程序的“命令名”,不同於真正的可執行文件的文件名,這個“命令名”是一個用來顯示的名稱,例如執行*nix下的 ps 命令,顯示出來的就是這個“命令名”。
#在*nix下,當shell=False(默認)時,Popen使用os.execvp()來執行子程序。args一般要是一個列表。如果args是個字符串的話,會被當做是可執行文件的路徑,這樣就不能傳入任何參數了。 

詳細可參考:http://luy.li/2010/04/14/python_subprocess/
我們來直接看一下我編寫的示例代碼,主程序(test_signal_send.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import subprocess
from subprocess import Popen
import signal
 
childs = []
 
def handler(signo, frame):
    global childs
    for child in childs:
        try:
            child.send_signal(signal.SIGINT)
        except:
            pass
 
def main():
    global childs
 
    for i in range(0,10):
        p = Popen(["./test_signal_recv"])
        childs.append(p)
 
    signal.signal(signal.SIGINT, handler)
 
    for child in childs:
        child.send_signal(signal.SIGUSR1)
 
    for child in childs:
        child.wait()
 
if __name__ == "__main__":
    main()

然後是處理進程(test_signal_recv.cpp)(沒有在ouch中直接printf的原因是由於printf在信號處理函數中調用不安全,詳細可以參考unix網絡編程):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <error.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h> 
#include <sys/wait.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <signal.h>
 
int getsig = 0;
void ouch(int sig)
{       
    getsig = 1;
}
int main(int argc, const char *argv[])
{
    signal(SIGUSR1,ouch);
    while(1)
    {
        printf("hello world\n");
        if (getsig == 1)
        {
            printf("get signal\n");
            getsig = 0;
        }
        sleep(1);
    }
    return 0;
}

通過

python test_signal_send.py

運行,在另一個窗口中輸入:

ps x

終端結果如下:

  PID TTY      STAT   TIME command
 2563 ?        S      0:00 sshd: dantezhu@pts/0
 2564 pts/0    Ss     0:00 -bash
 2778 ?        S      0:00 sshd: dantezhu@pts/1
 2779 pts/1    Ss     0:00 -bash
 3172 pts/0    S+     0:00 python test_signal_send.py
 3173 pts/0    S+     0:00 ./test_signal_recv
 3174 pts/0    S+     0:00 ./test_signal_recv
 3175 pts/0    S+     0:00 ./test_signal_recv
 3176 pts/0    S+     0:00 ./test_signal_recv
 3177 pts/0    S+     0:00 ./test_signal_recv
 3178 pts/0    S+     0:00 ./test_signal_recv
 3179 pts/0    S+     0:00 ./test_signal_recv
 3180 pts/0    S+     0:00 ./test_signal_recv
 3181 pts/0    S+     0:00 ./test_signal_recv
 3182 pts/0    S+     0:00 ./test_signal_recv
 3185 pts/1    R+     0:00 ps x

在主進程的窗口上輸入CTRL+C,再查看進程情況:

  PID TTY      STAT   TIME COMMAND
 2563 ?        S      0:00 sshd: dantezhu@pts/0
 2564 pts/0    Ss+    0:00 -bash
 2778 ?        S      0:00 sshd: dantezhu@pts/1
 2779 pts/1    Ss     0:00 -bash
 3187 pts/1    R+     0:00 ps x

OK,這樣主進程啟動處理進程的問題就解決了,怎麽樣,簡單吧!

接下來是數據通信的問題。
c處理進程間通信的常用方式相信大家都知道:共享內存,消息隊列,信號量,管道,信號,socket,文件mmap等。而python中只支持上述列表中的管道,信號,socket,文件mmap。
具體的篩選過程就不說了,只說最終選擇的方案是信號+文件mmap的方式。主進程發送給處理進程信號通知數據可讀,處理進程從文件mmap中讀取數據。
其實前面的例子中已經使用了信號,所以我們主要說一下mmap就行。python中是提供了mmap模塊的,我們直接調用即可。
寫文件mmap(python)(test_mmap_write.py):

1
2
3
4
5
6
7
8
import mmap
 
wtext = 'www.vimer.cn'
f = file('hello.txt','w+b')
f.truncate(len(wtext))
map = mmap.mmap(f.fileno(), len(wtext))
map.write(wtext)
map.flush()

讀文件mmap(c)(test_mmap_read.cpp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <error.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/mman.h>
using namespace std;
unsigned long get_file_size(const char *filename)
{
    struct stat buf;
    if(stat(filename, &buf)<0)
    {   
        return 0;
    }   
    return (unsigned long)buf.st_size;
}
 
 
int map_read()
{
    char file_name[] = {"hello.txt"};
    int length = get_file_size(file_name);
 
    int fd = open(file_name, O_RDWR | O_CREAT, 0644);
    if(fd < 0)  return -1; 
    char *buf = (char *) mmap(0, length, PROT_READ, MAP_SHARED, fd, 0); 
    if(buf == NULL)
    {   
        close(fd);
        return -1; 
    }
    close(fd);
    printf("%s\n",buf);
}
int main(int argc, const char *argv[])
{
    map_read();
    return 0;
}

先運行:

python test_mmap_write.py

然後運行./test_mmap_read,輸出如下:

www.vimer.cn

OK,完美解決~~這些代碼都放到了fuload工程中,大家可以到https://fuload.Googlecode.com/svn/trunk/src/slave/test/查看源碼。


Tags: 進程 處理 None python executable 參數

文章來源:


ads
ads

相關文章
ads

相關文章

ad