美文网首页linux
Linux进程间的通信机制

Linux进程间的通信机制

作者: 捉虫大师 | 来源:发表于2015-04-08 17:59 被阅读968次

概述

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程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运行截图

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

相关文章

  • c进程间通信IPC以及信号概述

    linux系统内核提供了进程间通信的机制 IPC(InterProcess Communication) IPC的...

  • Linux进程间的通信机制

    概述 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通...

  • Android进阶笔记-5. IPC机制 & Binder 原理

    IPC机制 一次进程间通信至少包含两个进程,由于进程隔离机制的存在,通信双方必然需要借助 IPC(进程间通信,in...

  • linux 进程间通信手段介绍

    linux进程间支持多种通信机制进行通信、传递消息。常用的包括:管道、共享内存、消息队列、socket。代码参考自...

  • 初探Android中的binder机制

    Binder机制是Android系统中最主要的进程间通信机制。虽然Android底层使用的是linux内核,但是除...

  • 进程间通信

    Android系统是基于Linux内核的,而Linux内核继承和兼容了丰富的Unix系统进程间通信(IPC)机制。...

  • Android进程间通信

    3.5 Android进程间通信 3.5.1 背景知识 传统IPC Linux传统的IPC机制分为如下几种:管道、...

  • 进程间的通信

    进程间的通信主要分为本机器进程间的通信和不同机器间进程的通信。本文主要描述本机进程间的通信。 一、传统Linux的...

  • 【Linux】Linux的管道

    管道是Linux由Unix那里继承过来的进程间的通信机制,它是Unix早期的一个重要通信机制。其思想是,在内存中创...

  • 【python】进程间通信:Queue的详细用法

    关于python 进程间通信 Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信。 进程间通...

网友评论

    本文标题:Linux进程间的通信机制

    本文链接:https://www.haomeiwen.com/subject/comzxttx.html