Linux进程间的通信机制

概述

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

进程间的通信.png

管道通信

管道是一种最基本的通信机制。
在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,进程1往管道中写数据,进程2读管道中的数据,这样就实现了进程间的通信。对用户程序来说,管道就像一个文件。
创建管道的函数:

#include<unistd.h>
int pipe(int filedes[2]);

调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。

管道通信的过程:

  • 父进程创建管道;
  • 父进程fork出子进程;
  • 父进程关闭fileds[0],子进程关闭fileds[1];

管道是用队列实现的,由于是队列,故在两个进程间通信只能由一端到另一端,如果要双向通信则需创建两个管道。由于是通过文件描述符来指向管道的,那么只能在父子进程间通信。

写个简单的例子

#include<stdio.h>
#include<unistd.h>

int main(void)
{
    int fd[2];
    int n;
    pid_t pid;
    char line[80];
    pipe(fd);
    pid = fork();
    if(pid > 0){
        close(fd[0]);
        write(fd[1], "hello world\n", 12);
        waitpid(pid,NULL,0); 
    }else if(pid == 0){
        close(fd[1]);
        n = read(fd[0], line, 80);
        printf("msg:%s", line);
    }
    return 0;
}
运行截图.png

其他通信机制-FIFO

用命令行来测试:

先创建一个FIFO文件

mkfifo test

然后读取FIFO的内容,此时因为FIFO为空,所以会挂起

cat < ./test

打开另一个终端往test中写入信息

echo "just a test" > ./test

这时你会发现第一个终端会输出"just a test"并且退出。

用程序测试:

(1)和命令行一样采取阻塞式,代码如下
写FIFO

//write.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#define FIFO_FILE "./myfifo"

int main()
{
    int fd = 0;
    int n;
    int re;
    char buf[15] = "hello world\n";
    unlink( FIFO_FILE );
    re = mkfifo( FIFO_FILE, 0777 );
    fd = open(FIFO_FILE, O_WRONLY);
    if(fd >= 0){
        n = write(fd, buf, 15);
        close(fd);
        printf("%d writed\n", n);
    }else{
        perror("open error");
        exit(1);
    }
    return 0;
}

读FIFO

//read.c
#include<stdio.h> 
#include<stdlib.h> 
#include<unistd.h> 
#include<sys/types.h> 
#include<sys/stat.h> 
#include<fcntl.h> 
#include<errno.h>
#define FIFO_FILE "./myfifo"

int main()
{
    char buf[80];
    int n = 0;
    int fd;
    fd = open(FIFO_FILE, O_RDONLY);
    if(fd < 0){
        perror("open error");
        exit(-1);
    }
    n = read(fd, buf, 80);
    close(fd);
    printf("n = %d\n", n);
    printf("msg:%s\n", buf);
    return 0;
}

运行

./write &
./read &

编译运行截图

编译运行阻塞式FIFO

(2)非阻塞式
代码稍微修改一下
写FIFO
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#define FIFO_FILE "/home/lkxiaolou/c/fifo/myfifo"

int main()
{
    int fd = 0;
    int n;
    int re;
    char buf[15] = "hello world\n";
    fd = open(FIFO_FILE, O_WRONLY|O_NONBLOCK);
    if(fd >= 0){
        n = write(fd, buf, 15);
        close(fd);
        printf("%d writed\n", n);
    }else{
        perror("open error");
        exit(1);
    }
    return 0;
}

读FIFO

#include<stdio.h> 
#include<stdlib.h> 
#include<unistd.h> 
#include<sys/types.h> 
#include<sys/stat.h> 
#include<fcntl.h> 
#include<errno.h>
#define FIFO_FILE "/home/lkxiaolou/c/fifo/myfifo"

int main()
{
    char buf[80];
    int n = 0;
    int fd;
    int re;
    unlink( FIFO_FILE );
    re = mkfifo( FIFO_FILE, 0777 );
    fd = open(FIFO_FILE, O_RDONLY | O_NONBLOCK);
    if(fd < 0){
        perror("open error");
        exit(-1);
    }
    while(n == 0){
        n = read(fd, buf, 80);
        sleep(1);
    }
    close(fd);
    printf("n = %d\n", n);
    printf("msg:%s\n", buf);
    return 0;
}

编译运行结果

编译运行非阻塞式FIFO

其他通信机制-UNIX Domain Socket

socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制

由于是全双工,则可以互相通信,不存在读写端,这里称为服务端和客户端。

服务端工作流程:

  • socket 建立一个socket
  • bind 将这个socket绑定在文件(端口)上
  • listen 开始监听
  • accept 如果客户端连接,则接受并建立一个新的socket来和客户端通信
  • read/write 读取或者发送消息
  • close 关闭连接

客户端工作流程:

  • socket 建立一个socket
  • connect 主动连接服务端的文件(端口)
  • read/write 接收或者发送消息
  • close 关闭连接

找了一个例子,稍微修改了一下:

server代码:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>

// the max connection number of the server
#define MAX_CONNECTION_NUMBER 5

/* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */
int unix_socket_listen(const char *servername)
{ 
    int fd;
    struct sockaddr_un un; 
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){
        return(-1); 
    }
    int len, rval; 
    unlink(servername);               /* in case it already exists */ 
    memset(&un, 0, sizeof(un)); 
    un.sun_family = AF_UNIX; 
    strcpy(un.sun_path, servername); 
    len = offsetof(struct sockaddr_un, sun_path) + strlen(servername); 
    /* bind the name to the descriptor */ 
    if (bind(fd, (struct sockaddr *)&un, len) < 0){ 
        rval = -2; 
    } else{
        if (listen(fd, MAX_CONNECTION_NUMBER) < 0){ 
            rval =  -3; 
        }else{
            return fd;
        }
    }
    int err;
    err = errno;
    close(fd); 
    errno = err;
    return rval;  
}

int unix_socket_accept(int listenfd, uid_t *uidptr)
{ 
    int clifd, len, rval; 
    time_t staletime; 
    struct sockaddr_un un;
    struct stat statbuf; 
    len = sizeof(un); 
    if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0){
        return(-1);     
    }
    /* obtain the client's uid from its calling address */ 
    len -= offsetof(struct sockaddr_un, sun_path);  /* len of pathname */
    un.sun_path[len] = 0; /* null terminate */ 
    if (stat(un.sun_path, &statbuf) < 0){
        rval = -2;
    }else{
        if (S_ISSOCK(statbuf.st_mode) ){ 
            if (uidptr != NULL) *uidptr = statbuf.st_uid;    /* return uid of caller */ 
            unlink(un.sun_path);       /* we're done with pathname now */ 
            return clifd;      
        }else{
            rval = -3;     /* not a socket */ 
        }
    }
    int err;
    err = errno; 
    close(clifd); 
    errno = err;
    return(rval);
 }
 
void unix_socket_close(int fd)
{
    close(fd); 
}

int main(void)
{ 
    int listenfd,connfd; 
    listenfd = unix_socket_listen("foo.sock");
    if(listenfd<0){
        printf("Error[%d] when listening...\n",errno);
        return 0;
    }
    printf("Finished listening...\n",errno);
    uid_t uid;
    connfd = unix_socket_accept(listenfd, &uid);
    unix_socket_close(listenfd);  
    if(connfd<0){
        printf("Error[%d] when accepting...\n",errno);
        return 0;
    }  
    printf("Begin to recv/send...\n");  
    int i,n;
    int size = 0;
    char rvbuf[2048];
    while(size == 0)
    {
        //===========接收==============
        size = recv(connfd, rvbuf, 12, 0);  
        if(size>0){
            printf("Recieved Data[%d]:%s\n",size,rvbuf);
        }
        if(size==-1){
            printf("Error[%d] when recieving Data:%s.\n",errno,strerror(errno));
        }
    }
    unix_socket_close(connfd);
    printf("Server exited.\n");    
}

client代码:

#include <stdio.h>
#include <stddef.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>

/* Create a client endpoint and connect to a server.   Returns fd if all OK, <0 on error. */
int unix_socket_conn(const char *servername)
{ 
    int fd; 
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){   /* create a UNIX domain stream socket */ 
        return(-1);
    }
    int len, rval;
    struct sockaddr_un un;          
    memset(&un, 0, sizeof(un));            /* fill socket address structure with our address */
    un.sun_family = AF_UNIX; 
    sprintf(un.sun_path, "scktmp%05d", getpid()); 
    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
    unlink(un.sun_path);               /* in case it already exists */ 
    if (bind(fd, (struct sockaddr *)&un, len) < 0){ 
        rval=  -2; 
    }else{
        /* fill socket address structure with server's address */
        memset(&un, 0, sizeof(un)); 
        un.sun_family = AF_UNIX; 
        strcpy(un.sun_path, servername); 
        len = offsetof(struct sockaddr_un, sun_path) + strlen(servername); 
        if (connect(fd, (struct sockaddr *)&un, len) < 0) {
            rval= -4; 
        }else{
            return (fd);
        }
    }
    int err;
    err = errno;
    close(fd); 
    errno = err;
    return rval;    
}
 
void unix_socket_close(int fd)
{
    close(fd);     
}


int main(void)
{ 
    srand((int)time(0));
    int connfd; 
    connfd = unix_socket_conn("foo.sock");
    if(connfd<0){
        printf("Error[%d] when connecting...",errno);
        return 0;
    }
    printf("Begin to recv/send...\n");  
    int i,n,size;
    //=========发送======================
    char rvbuf[12]="hello world";
    size = send(connfd, rvbuf, 12, 0);
    if(size>=0){
        printf("Data[%d] Sended:%s\n",size,rvbuf);
    }
    if(size==-1){
        printf("Error[%d] when Sending Data:%s.\n",errno,strerror(errno)); 
    }
    unix_socket_close(connfd);
    printf("Client exited.\n");    
}

编译运行截图:

unix domain socket运行截图

好了,进程通信就写到这吧,累死我了--!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 一.管道机制(pipe) 1.Linux的fork操作 在计算机领域中,尤其是Unix及类Unix系统操作系统中,...
    Geeks_Liu阅读 3,690评论 1 9
  • Android跨进程通信IPC整体内容如下 1、Android跨进程通信IPC之1——Linux基础2、Andro...
    隔壁老李头阅读 15,586评论 19 113
  • 一、进程间通信的概念 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间...
    TyiMan阅读 166,314评论 16 318
  • 1 进程介绍 1.1 进程和程序 所谓进程是由正文段(text)、用户数据段(user segment)以及系统数...
    疯狂小王子阅读 1,241评论 0 7
  • 就只有一堆土和我作伴。慢慢就养成了挖土的习惯,手掌粘满了土,合该洗洗,转个弯又去了土堆。我挖呀挖,什么都没有挖到手...
    angelican阅读 423评论 1 1