C进程通信(管道,信号,有名,匿名管道,信号,mmap,本地套接字)

管道

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/fcntl.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

/**
 * 进程通信(IPC)
 * 因为整个操作系统中,内核只有一块,所以采用内核共享缓冲区,实现进程通信
 *
 *
 * 管道
 *  作用与父子进程
 *  本质是一个伪文件(内核缓冲区,大小4K,环形队列实现)
 *  2个文件描述符引用,一个表示读端,一个表示写端
 *
 * 双向半双工
 *  只能在有公共祖先进程间才能使用
 *
 *  读
 *      管道有数据,返回数据
 *      无数据
 *          写端全部关闭,返回0,表示读到末尾
 *          写端没关闭,则阻塞.
 *  写
 *      读端全部关闭,则进程异常终止(收到SIGPIPE信号)
 *      没关闭
 *          管道满了,则阻塞
 *          没满,写入数据
 */

void test() {
    int fd[2];
    char *str = "hello World";
    char buf[1024];
    int ret = pipe(fd);

    if (ret == 1) perror(" error");

    pid_t pid = fork();
    if (pid > 0) {
        //关闭读端
        close(fd[0]);
        write(fd[1], str, strlen(str));
        close(fd[1]);
    } else if (pid == 0) {
        //子进程关闭写端
        close(fd[1]);
        int size = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, size);
        close(fd[0]);

    }
}

void test1() {
    int fd[2];
    int ret = pipe(fd);
    if (ret == 1) perror(" error");
    pid_t pid = fork();
    /* 这里之所以主进程负责读,然后执行命令
     当主进程负责写,子进程负责读时
     子进程会一直阻塞等待缓冲区数据.
     这样就会造成父进程永远先于子进程结束,然后子进程会与bash进程争抢输出,从而乱序.
     这里让父进程sleep也是无效的,因为exec写入数据是新开的一个新进程,无法控制它的同步输入缓冲区.
     让父进程负责读操作就行了.*/
    if (pid > 0) {
        //关闭写端
        close(fd[1]);
        //将控制台输入数据指向缓冲区
        //也就是当执行wc时,会读取控制台输入数据,这个时候控制台指向缓冲区
        //因此读取的是缓冲区的数据
        dup2(fd[0], STDIN_FILENO);
        execlp("wc", "wc", "-l", NULL);

        //这里不再执行close,因为缓冲区交由execlp处理了,只能期待内核隐式回收.
//        close(fd[0]);
    } else if (pid == 0) {
        //子关闭读端
        close(fd[0]);
        //将控制台输出数据,写入缓冲区
        dup2(fd[1], STDOUT_FILENO);
        execlp("ls", "ls", NULL);
    }
}


void test2() {
    int fd[2];
    int ret = pipe(fd);
    if (ret == 1) perror(" error");
    int i;
    for (i = 0; i < 2; i++) {
        pid_t pid = fork();
        if (pid == 0)break;
    }
    if (i == 0) {
        //关闭写端
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        execlp("ls", "ls", NULL);
    } else if (i == 1) {
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("wc", "wc", "-l", NULL);
    } else if (i == 2) {
        //只能存在一个读端和写端
        close(fd[0]);
        close(fd[1]);
        wait(NULL);
        wait(NULL);
    }
}

void test3() {
    char *str1 = "hello World1";
    char *str2 = "hello World2";
    char buf[1024];

    int fd[2];
    int ret = pipe(fd);
    if (ret == 1) perror(" error");
    int i;
    for (i = 0; i < 2; i++) {
        pid_t pid = fork();
        if (pid == 0)break;
    }
    if (i == 0) {
        close(fd[0]);
        write(fd[1], str1, strlen(str1));
        close(fd[1]);
    } else if (i == 1) {
        close(fd[0]);
        write(fd[1], str2, strlen(str2));
        close(fd[1]);
    } else if (i == 2) {
        sleep(1);
        close(fd[1]);
        int size = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, size);
        close(fd[0]);

        wait(NULL);
        wait(NULL);
    }

}

int main() {
/* 管道通信 */
//    test();
//父子通信执行ls | wc -l
//    test1();

//兄弟进程通信
//    test2();

//多端写入,一端读取
//    test3();
    return 0;
}


有名管道

**file**
```c
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/fcntl.h>

/**
 * 有名管道,可不同进程通信
 * 数据只能读取一次
 * linux下mkfifo命令
 * @return
 */
int main() {
/*    int fd = mkfifo("test2", 644);  //创建一个有名管道
    printf("111111111111 %d \n", fd);*/

    char buf[1024];
    int fd = open("E:\\workspace\\c\\cmake-build-debug\\test", O_WRONLY);
    int i = 0;
    while (1) {
        sleep(3);
        sprintf(buf, "hello %d \n", i++);
        write(fd, buf, strlen(buf));
    }
    close(fd);
    return 0;
}

file2

#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/fcntl.h>

/**
 *有名管道
 * @return
 */
int main() {
    char buf[1024];
    int fd = open("E:\\workspace\\c\\cmake-build-debug\\test", O_RDONLY);
    printf("11122222222222222211%d \n", fd);
    while (1) {
        sleep(3);
        int size = read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, size);
    }
    close(fd);
    return 0;
}

文件通信

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/fcntl.h>
#include <string.h>

/**
 * 文件通信
 * @return
 */
int main() {
    int te = 100;
    int i;
    pid_t pid;
    for (i = 0; i < 2; i++) {
        if ((pid = fork()) == 0)break;
    }
    if (i == 0) {
       int fd = open("aa.txt", O_RDWR | O_TRUNC | O_CREAT,0664);
        char *str = "hello \n";
        write(fd, str, strlen(str));
        sleep(4);
        lseek(fd,0,SEEK_SET);
        char buf[1024];
        int ret = read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);
    } else if (i == 1) {
        sleep(2);
        int fd = open("aa.txt", O_RDWR);
        char buf[1024];
        int ret = read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);

        char *str = "world \n";
        write(fd, str, strlen(str));

    } else {
        sleep(10);
        printf("我是父进程 %d \n", te);
    }
    return 0;
}

信号


#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <wait.h>

/**
 * 信号
 *      满足条件才能发送
 * 每个进程收到的所有信号,都是内核负责发送的.
 * 类似硬件中断
 *
 * 产生信号原因
 *      按键产生,如CTRL+X
 *      系统调用,如KILL,ABORT
 *      软件产生,如定时器,sleep
 *      硬件异常,1/0,内存对齐错误
 *      命令产生,kill命令
 *
 * 递达:信号到达目标进程
 * 未决:处于产生和递达中间状态,主要由于阻塞(屏蔽)导致
 * 处理
 *         1:执行默认动作
 *         2:忽略
 *         3:捕捉(调用用户处理函数)
 *
 * 阻塞信号集和未决信号集 位图形式 存储PCB中
 * 阻塞信号集:将信号加入集和,对他们设置屏蔽,除非解除屏蔽才会执行此信号
 * 未决信号集:信号产生后,未决信号集中描述该信号的位标记1,当信号处理后,重置0.
 *           在信号产生后由于某些原因(如阻塞)不能抵达,则称之为未决信号.解除屏蔽前,一直处于未决状态.
 *
 * kill -l查看信号
 * kill -信号编号  执行默认动作
 * 信号不能忽略
 *      SIGSTOP
 *      SIGKILL
 * 用户自定义信号
 *      SIGUSR1/USERR2
 * 子进程状态变化通知父进程,默认动作忽略
 *      SIGCHLD信号产生条件
 *               子进程终止时
 *               子进程收到SIGSTOP信号停止时
 *               子进程处于停止态,接收到SIGCONT后唤醒时
 * 按键产生的信号
 *      SIGINT      Ctrl+c      终止/中断
 *      SIGTSTP     Ctrl+z      暂停/停止
 *      SIGQUIT     Ctrl+\      退出
 * 硬件异常信号
 *      SIGFPE      浮点数错误,如除0
 *      SIGSEGV     段错误,非法访问内存
 *      SIGBUS      总线错误
 *
 *
 *
 * @return
 */

void killTest() {
    pid_t pid = fork();
    if (pid == 0) {
        /*
       给指定进程发送信号
            pid>0,发送指定进程
            =0,发送信号给与调用kill函数进程属于同一进程组的所有进程
            <-1,取pid发送对应进程组
            =-1,发送给进程有权限发送的系统中所有进程
        */
//        kill(getppid(), SIGCHLD);
    } else if (pid > 0) {

    }
}

void myfunc(int sign) {
    switch (sign) {
        case SIGALRM:
            printf("捕捉到了SIGALRM:%d \n", sign);
            break;
    }
}

/**
 * 设置定时
 * 以及捕捉信号,执行回调函数.
 */
void alarmTest() {
    /* 一个进程只有一个定时器,存PCB中
  *      延时多少秒发送SIGALRM信号,给当前执行进程
  *      返回上次设置的延时到如今,还剩下多少秒数
     *
     *      time 命令可查看程序运行时间
     *              实际时间=用户时间+内核时间+等待时间
  */

    alarm(1);

    //捕捉信号,以及执行方法
//    signal(SIGALRM, myfunc);
    struct sigaction act, old;
    //设置捕捉后回调方法
    act.sa_handler = myfunc;

    /** 捕捉函数执行期间,本信号自动屏蔽.
     sa_mask跟程序阻塞信号集二者不可同日而语,samask生命周期只在回调函数执行期间,执行完后.会恢复阻塞信号集.
     阻塞的常规信号不支持排队,当捕捉函数执行结束,恢复阻塞信号集后,多次执行的信号只会执行一次.(linux kill -l 手册中后32信号支持排队)*/


    //设置捕捉函数执行期间,屏蔽的信号集
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGQUIT);
    /**
     * 中断系统调用:
     *      系统调用分为2种,慢速系统和其他系统调用
     * 慢系统调用
     *      可能造成永远阻塞的情况,如:wait()或者write()read()...
     *      这个时候执行中断后,当中断回调函数执行结束,应该返回到阻塞函数.
     *      可设置flags=SA_RESTART
     *  sigaction:
     *      sa_flags:
     *        SA_RESTART:系统中断后重启
     *        SA_NODEFER:不屏蔽当前信号
     *
     *  其他系统调用:
     *      getPid,fork
     */
    //设置0,表示回调执行期间,屏蔽捕捉的当前信号
    //之所有这个参数,是为了避免,回调函数执行期间,捕捉了当前信号,然后递归.内存溢出.
    act.sa_flags = 0;


    int ret = sigaction(SIGALRM, &act, &old);
    if (ret == -1) {
        perror("error");
    }
    for (int i = 0;; ++i) {
        sleep(1);
        printf("%d \n", i);
    }
}


int setitimerTest() {
    struct itimerval it, oldit;

    signal(SIGALRM, myfunc);

    //第一次执行定时2秒钟,只有每次间隔5S执行一次定时器
    //定时时长
    it.it_value.tv_sec = 2;
    it.it_value.tv_usec = 0;
    //2次定时任务之间间隔时间
    it.it_interval.tv_sec = 5;
    it.it_interval.tv_usec = 0;

    /*
     * ITIMER_REAL      发送SIGLARM信号        计算自然时间
     * 	ITIMER_VIRTUAL	SIGVTALRM             计算进程占用CPU时间
        ITIMER_PROF     SIGPROF               计算占用CPU时间+系统调用时间

        it:设置的定时时间
        oldit:传出参数,记录上次剩余时间


     */
    if (setitimer(ITIMER_REAL, &it, &oldit) == -1) {
        perror("error");
        return -1;
    }
    while (1);
}

/**
 * 设置未决信号和阻塞信号集
 */
void sigset() {
    sigset_t set, oldset, pedset;
    //清空信号集
    sigemptyset(&set);
    //将一个信号添加到集合中
    sigaddset(&set, SIGINT);
    //设置信号屏蔽字
    /*SIG_BLOCK:设置阻塞
    /*SIG_UNBLOCK:设置不阻塞
    /*SIG_SETMASK:自定义set替换mask

     oldset:旧的mask
     */
    int ret = sigprocmask(SIG_BLOCK, &set, &oldset);
    if (ret == -1) {
        perror("error");
    }
    while (1) {
        sleep(1);
        //查看未决信号集
        ret = sigpending(&pedset);
        if (ret == -1) {
            perror("error");
        }
        //遍历打印
        for (int i = 0; i < 32; i++) {
            //判断一个信号是否在未决信号集合中
            if (sigismember(&pedset, i)) {
                putchar('1');
            } else {
                putchar('0');
            }
        }
        printf("\n");
    }
}

void myfunc2(int sign) {
    pid_t pid;
    //当同一个时间点,多个进程死了,发送信号.当回调函数执行结束后,只会执行一个.
//     pid = wait(NULL);

//每次执行回收多个子进程
    int status;
    pid_t ip;
//    while ((pid = wait(NULL)) != -1)
    while ((ip = waitpid(-1, &status, WNOHANG)) != -1) {
        if (WIFEXITED(status)) {
            printf("子进程正常终止%d  返回的值为:%d \n", ip, WEXITSTATUS(status));
        }
    }

}

int clearChild() {
    //设置阻塞
    sigset_t set, oldset;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    int ret = sigprocmask(SIG_BLOCK, &set, &oldset);
    if (ret == -1) {
        perror("error");
    }


    pid_t pid;
    int i;
    for (i = 0; i < 5; i++) {
        if ((pid = fork()) == 0)break;

    }
    if (5 == i) {
        sleep(4);
        struct sigaction act;
        act.sa_handler = myfunc2;
        sigemptyset(&act.sa_mask);
        /**
         *
         */
        act.sa_flags = 0;
        sigaction(SIGCHLD, &act, NULL);

        //恢复阻塞
/* int ret = sigprocmask(SIG_SETMASK, &oldset, NULL);
        if (ret == -1) {
            perror("error");
        }*/
        sigprocmask(SIG_UNBLOCK, &set, NULL);

        printf("我是父进程 \n");
        //父进程先结束,注册的捕捉信号就清空了,这里让父进程不要结束.
        while (1);
    } else {
//        sleep(1);
        printf("我是子进程:%d \n", getpid());
        return i;
    }
}

int main() {
    //操作kill函数
    //    killTest();


/* 设置定时
 以及捕捉信号,执行回调函数.*/
//    alarmTest();
    //操作重复定时
//        setitimerTest();

    //设置未决信号和阻塞信号集
    //    sigset();



    //回收子进程
    return clearChild();
    return 0;
}

MMAP

地址

本地套接字

server.c

#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/un.h>

#define  PATH "serv.socket"

/**
 * 本地套接字
 * @return
 */
int main() {
    int lfd, cfd;
    int ret, len;
    char buf[BUFSIZ];
    struct sockaddr_un serv_addr, cliaddr;
    /**
     声明本地套接字 AF_UNIX/AF_LOCAL
     */
    lfd = socket(AF_UNIX, SOCK_STREAM, 0);

    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, PATH);

    //算出serv_addr的具体占用长度
    len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path);
    unlink(PATH);

    /**
     * 这里的长度,传递的不是结构体的大小,而是结构体内容占用空间大小.
     */
    bind(lfd, (struct sockaddr *) &serv_addr, len);
    listen(lfd, 20);
    printf("accept...\n");

    while (1) {
        //监听并处理连接
        len = sizeof(cliaddr);
        cfd = accept(lfd, (struct sockaddr *) &cliaddr, (socklen_t *) &len);

        len -= offsetof(struct sockaddr_un, sun_path);
        cliaddr.sun_path[len] = '\0';
        printf("fileName %s \n", cliaddr.sun_path);

        while ((ret = read(cfd, buf, sizeof(buf))) > 0) {
            write(STDOUT_FILENO, buf, ret);
            for (int i = 0; i < ret; ++i) {
                buf[i] = toupper(buf[i]);
            }
            write(cfd, buf, ret);
        }
        close(cfd);
    }
    close(lfd);

    return 0;
}

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/un.h>

#define  PATH "cli.socket"
#define  SER_PATH "serv.socket"


int main() {
    int  cfd;
    int ret, len;
    char buf[BUFSIZ];
    struct sockaddr_un clit_addr, serv_addr;

    cfd = socket(AF_UNIX, SOCK_STREAM, 0);

    bzero(&clit_addr, sizeof(clit_addr));
    clit_addr.sun_family = AF_UNIX;
    strcpy(clit_addr.sun_path, PATH);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(clit_addr.sun_path);

    //确保bind之前,套接字文件不存在.
    unlink(PATH);
    //这里不能依赖于隐式绑定,需要显示绑定,从而创建套接字文件
    bind(cfd, (struct sockaddr *) &clit_addr, len);

    
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, SER_PATH);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path);
    connect(cfd, (struct sockaddr *) &serv_addr, len);

    while (1) {
        write(cfd, "hello\n", 6);
        sleep(1);
        ret = read(cfd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);
    }
    close(cfd);
    return 0;
}

如何选择?

1.管道:速度慢,容量有限,半双工,只有父子进程能通讯 ,FIFO,不能重复消费.

2.FIFO(命名管道):基本同上,但任何进程间都能通讯

3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

4.信号量:不能传递复杂消息,只能用来同步

5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

6.socket:稳定,可靠,全双工,任何进程都可通信,不可重复消费

7.文件通信(舍弃):慢,可使用偏移量重复消费,需要处理同步问题.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值