python与c-跨语言级别的进程间通信
今天发文比较多,哈,实在是觉得知识就该及时沉淀下来,时间长了难免记忆会模糊。
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/查看源码。
建议继续学习:
- websocket 通信协议 (阅读:5406)
- Nginx的master和worker进程间的通信 (阅读:3640)
- 即时通信与浏览器多TAB通信 (阅读:3550)
- 360软件管家通信协议分析 (阅读:3305)
- 操作系统基础知识 (阅读:1797)
- 通信复杂度问题:确定双方手中所有数的中位数 (阅读:1429)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:Dante 来源: Vimer
- 标签: 通信
- 发布时间:2010-11-30 22:47:33
- [44] 界面设计速成
- [42] Oracle MTS模式下 进程地址与会话信
- [41] android 开发入门
- [41] 如何拿下简短的域名
- [41] 图书馆的世界纪录
- [39] IOS安全–浅谈关于IOS加固的几种方法
- [39] 视觉调整-设计师 vs. 逻辑
- [37] 【社会化设计】自我(self)部分――欢迎区
- [37] 程序员技术练级攻略
- [35] 读书笔记-壹百度:百度十年千倍的29条法则