linux ipc

  • 查看系统消息队列,共享内存、信号量的命令。
  • picrm -m “id” 删除共享内存
  • picrm -s “id” 删除信号量
  • picrm -m “id” 删除消息队列

1. 无名管道

  • 无名/有名管道都属于文件,存在于内存中,管道通信实则是在对文件进行读写操作 read write

  • 两者都具有管道特性,先进先出,追加写文件,从头读文件并清楚已读内容

  • 无名管道

    • 只能亲缘关系进程间使用
    • 读写端同时存在,半双工。
    • 根据管道特性,虽然可以读写同时存在,但是通常读写都是相对的
    • 为了避免自写自读的情况发生,我们只保留一组读写端进行单工通信
    • 数据读后小时,不可进行二次读取
    • 读写方一旦确定无法更改,因为调用close关闭文件描述符后就无法使用open打开会出错。
    • 读管道
      • 管道中有数据:read 返回实际读到的字节数
      • 管道中无数据
        • 管道写端被全部关闭:read返回0(好像读到文件结尾)
        • 写端没有被全部关闭:read阻塞等待数据到来
    • 写管道
      • 管道读端被全部关闭:进程异常终止(也可以使用捕捉SIGPIPE信号,使进程不终止)
      • 管道读端没有被全部关闭
        • 管道已满,write阻塞
        • 管道未满,write将数据写入,并返回实际写入的字节数
  • 有名管道

    • 无亲缘关系的管道间使用
    • 当打开一个FIFO时,产生下列影响:(通常默认阻塞)
      • 在一般情况中(没有说明非阻塞 O_NONBLOCK),读打开要阻塞到某个其他进程为写打开此FIFO。为写而打开一个FIFO要阻塞到某个其他进程为读而打开它
      • 若指定了O_NONBLOCK,为读打开,如果没有进程已经为写而打开此FIFO,则读打开立即返回。为写打开,如果没有进程已经为读而打开一个FIFO,那么将出错返回,其errno是ENXIO
    • O_RDONLY、O_WRONLY、O_RDWR均可用,所以可以同时往管道进行读写进行双工通信;
      根据管道特性,虽然可以读写同时存在,但是通常读写都是相对的,为了避免自写自读的情况发生,
      采用只读只写方式打开进行单工通信
    1. pipe创建管道
    2. fork创建进程
    3. 父进程往管道里写,子进程读管道里的数据并打印
  // gcc demo1.c    ./a.out
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main()
{
        int pid;
        int fd[2];
        char r_buf[128] = {0};

        if ((pipe(fd)) == -1) {		//创建管道
                perror("pipe fail");
                exit(-1);
        }

        if ((pid = fork()) == -1) {		//创建进程
                perror("fork fail");
                exit(-1);
        }

        if (pid > 0) {		//父进程
                close(fd[0]);	//关闭读端
                write(fd[1], "hallo world!", strlen("hallo world!"));	//往管道里写
                close(fd[1]);	//关闭管道
        } else if (pid == 0) {
                close(fd[1]);	//关闭写端
                read(fd[0], r_buf, 128);	//读取管道中的数据
                printf("%s\n", r_buf);		//打印数据
                close(fd[0]);		//关闭管道
        }

        return 0;
}

在这里插入图片描述

2. 有名管道 FIFO

  • 可以使用无亲缘进程之间
  • FIFO是一种文件类型,mkfifo创建,可以使用open打开,可以使用文件I/0函数
  • 打开FIFO以下影响,默认阻塞
    • 在一般情况中(没有说明非阻塞 O_NONBLOCK),读打开要阻塞到某个其他进程为写打开此FIFO。为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。
    • 若指定了O_NONBLOCK,为读打开,如果没有进程已经为写而打开此FIFO,则读打开立即返回。
      为写打开,如果没有进程已经为读而打开一个FIFO,那么将出错返回,其errno是ENXIO。
  1. mkfifo创建FIFO
  2. fork创建进程
  3. 父进程只写打开FIFO,并往FIFO里写如数据
  4. 子进程只读打开FIFO,并读取FIFO里的数据并打印
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

pathname	创建一个名字为"pathname"的FIFO特殊文件
mode	文件权限

返回值:成功返回 0   失败返回 -1 ,errno被设置
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define FIFO_FILE "/tmp/my_fifo"

int main()
{
        int fd;
        int pid;
        int sum = 3;
        char r_buf[128] = {0};

        if (((mkfifo("FIFO_FILE", 0600)) == -1) && (errno != EEXIST)) {	//创建FIFO,如果FIFO存在条件为假
                perror("mkfifo fail");
                exit(-1);
        }

        if ((pid = fork()) == -1) {	//创建进程
                perror("fork fail");
                exit(-1);
        } else if (pid > 0) {	//父进程
                fd = open("FIFO_FILE", O_WRONLY);	//打开FIFO
                while(sum)	//间隔一秒往FIFO里写入数据
                {
                        write(fd, "message form fifo", strlen("message from fifo"));
                        sleep(1);
                        sum--;
                }
                close(fd);	//关闭FIFO
        } else if (pid == 0) {	//子进程
                fd = open("file", O_RDONLY);	//打开FIFO
                while (sum)		//间隔一秒读取一次FIFO里的数据并打印
                {
                        read(fd, r_buf, 128);
                        puts(r_buf);
                        sleep(1);
                        sum--;
                }
                close(fd);	//关闭FIFO
        }

        return 0;
}

在这里插入图片描述

3. 消息队列

  1. 消息队列是消息的链表,存放在内核中并由消息队列标识符标识索引。我们将称消息队列为“队列”,其标识符为“队列ID”
  2. 消息:每个消息包含一个类型(长整型)和一个数据部分(字节数组)
  3. 消息类型:用于区分不同类型的消息,接收进程可以根据消息类型选择接收特定类型的消息。
  4. 可以按照数据的类型进行指定读,消息队列属于顺序队列形式的结构,向队列里写的每一条消息,会追加到队列后面,读取一个就从队列里消除一个
  5. 写函数不会阻塞,除非队列里存放的消息数量已经满了,才会导致写阻塞
  6. 可离线通讯,读写端无需同时存在
头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
    
/****************************************************
  *功能:获取消息队列标识(id_num)
  *参数: 
  *			@key   		键值(key_t --> int)
  *			@msgflg		权限 0777
  *						IPC_CREAT	创建
  *						IPC_EXCL	存在返回EEXIST
  *返回值:成功返回消息队列ID,失败返回-1,errno
  ***************************************************/
int msgget(key_t key, int msgflg);



/***********************************************
      *功能:生成key值
      *参数: 
      *			@pathname   文件名
      *			@proj_id    char类型的值
      *返回值:成功返回生成key值,失败返回-1,errno
      **********************************************/	
key_t ftok(const char *pathname, int proj_id);


/*******************************************************
      *功能:设置消息队列
      *参数: 
      *		@msqid   消息队列标号
      *		@cmd     命令码  IPC_STAT(获取状态)   
      *						 IPC_RMID(删除消息队列)
      *		@buf	 属性信息结构体指针
      *返回值:成功返回0,失败返回-1
      ******************************************************/
int msgctl(int msqid, int cmd, struct msqid_ds *buf);


/*********************************************************
   *功能:向消息队列里发数据
   *参数: 
   *		@msqid   消息队列标号
   *		@msgp    消息包
   *		@msgsz   mtext的大小
   *		@msgflg  flags
   *				 0            阻塞
   *				 IPC_NOWAIT   非阻塞
   *				 MSG_NOERROR  
   *返回值:成功返回0,失败返回-1,errno 
   ********************************************************/
int msgsnd(int msqid, const void *msgp, size_t msgsz,\
           int msgflg);


/*********************************************************
   *功能:从消息队列里读数据
   *参数: 
   *		@msqid   消息队列标号
   *		@msgp    消息包
   *		@msgsz   mtext的大小
   *		@msgtyp	 消息类型
   *		@msgflg  flags
   *				 0            阻塞
   *				 IPC_NOWAIT   非阻塞
   *				 MSG_EXCEPT   
   *返回值:成功返回mtext的大小,失败返回-1,errno 
   ********************************************************/
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, \
               long msgtyp,int msgflg);
key由ftok生成,key为队列ID的组成部分
msgflg标志位(IPC_CREAT、IPC_EXCL、 IPC_NOWAIT)还应加上队列权限(读、写、执行)
msqid队列ID
msgp消息缓冲区指针(结构体指针)
flag为0表示阻塞方式(队列空间不够就会堵塞),设置IPC_NOWAIT 表示非阻塞方式
msgsz消息数据长度,不含数据类型长度4个字节
msgtyp据类型 type == 0 返回队列中的第一个消息。type > 0 返回队列中消息类型为type的第一个消息。type < 0返回队列中消息类型值小于或等于type绝对值,而且在这种消息中,其类型值又最小的消息
cmd控制命令(IPC_STAT(读取数据结构)、IPC_SET(设置数据元素)、IPC_RMID(删除队列))
buf读取和设置操作的缓冲区指针,不使用设置为NULL
  1. fork创建进程
  2. ftok创建key
  3. 父进程创建消息队列,并往消息队列里写入数据
  4. 子进程打开消息队列,读取数据并打印,最后删除该消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

struct msgbuf {
        long mtype;
        char mtext[128];
};

int main()
{
        int key;
        int pid;
        int msgid;
        struct msgbuf wbuf;
        struct msgbuf rbuf;

        pid = fork();	//创建进程
        if (pid == -1) {
                perror("fork fail");
                exit(1);
        }
//系统建立IPC通讯(如消息队列、共享内存等)  必须指定一个ID值。通常情况下,该id值通过ftok函数得到
        key = ftok(".", 1);	//创建key
        if (key == -1) {
                perror("ftok fail");
                exit(2);
        }

        if (pid > 0) {	//父进程
                msgid = msgget(key, IPC_CREAT | 0777);	//创建消息队列
                if (msgid == -1) {
                        perror("msgget fail");
                        exit(3);
                }
                wbuf.mtype = 1;		//消息类型
                memset(wbuf.mtext, '0', sizeof(wbuf.mtext));
                strcpy(wbuf.mtext, "message from message queuing");
                msgsnd(msgid, &wbuf, sizeof(wbuf) - sizeof(long), 0);	//发送数据,空间不够就会堵塞
        } else if (pid == 0) {	//子进程
                msgid = msgget(key, IPC_CREAT | 0777);	//打开消息队列
                if (msgid == -1) {
                        perror("msgget fail");
                        exit(4);
                }
                msgrcv(msgid, &rbuf, sizeof(rbuf) - sizeof(long), 1, 0);	//读取数据,无数据就会堵塞
                printf("type = %ld  msg: %s\n", rbuf.mtype, rbuf.mtext);
                msgctl(msgid, IPC_RMID, NULL);	//删除消息队列
        }

        return 0;
}

在这里插入图片描述

4. 共享内存

  • 共享存储允许两个或多个进程共享一给定的存储区。因为数据不需要在客户机和服务器之间复制,所以这是最快的一种 IPC
  • 使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取
  • 若服务器将数据放入共享存储区,则在服务器做完这一操作之前,客户机不应当去取这些数据。通常,信号量被用来实现对共享存储存取的同步。
  1. fork创建进程
  2. ftok获取key
  3. 父进程shmget创建共享内存,shmat挂载共享内存到进程当中,strcpy写入数据,shmdt卸载共享内存
  4. 子进程shmget创建共享内存,shmat挂载共享内存到进程当中,打印数据,shmdt卸载共享内存,shmctl删除共享内存
#include <sys/ipc.h>
#include <sys/shm.h>

创建或获取共享内存并获取共享内存标识符,成功返回共享内存ID,失败返回-1,errno被设置
int shmget(key_t key, size_t size, int shmflg);
//size  大于0的整数:新建的共享内存大小,以字节为单位,0:只获取共享内存时指定为0
//shmflg  0:取共享内存标识符,若不存在则函数会报错  IPC_CREAT   IPC_CREAT | IPC_EXCL      还应加上内存权限(读、写)

将共享内存挂载到进程的内存空间,成功返回指向共享内存的指针,失败返回-1,errno被设置
void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmaddr   指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
//shmflg    0:内存权限可读可写      SHM_RDONLY:为只读模式

卸载共享内存,成功返回0,失败返回-1,errno被设置
int shmdt(const void *shmaddr);

删除共享内存,成功返回0,失败返回-1,errno被设置
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//cmd     	IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中  
//			IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
//			IPC_RMID:删除这片共享内存
//buf:  	 是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
        int pid;
        int key;
        int shmid;
        char *shmaddr;

        pid = fork();	//创建进程
        if (pid == -1) {
                perror("fork fail");
                exit(-1);
        }

        key = ftok(".", 2);	//获取key
        if (key == -1) {
                perror("ftok fail");
                exit(-1);
        }

        if (pid > 0) {	//父进程
                shmid = shmget(key, 1024*4, IPC_CREAT | 0666);	//创建共享内存
                if (shmid == -1) {
                        perror("shmget fail");
                        exit(-1);
                }
                shmaddr = shmat(shmid, NULL, 0);	//挂载内存到进程空间中
                strcpy(shmaddr, "message from share memory");	//写入数据
                shmdt(shmaddr);	//卸载共享内存
        } else if (pid == 0) {	//子进程
                shmid = shmget(key, 0, IPC_CREAT);	//获取共享内存ID
                if (shmid == -1) {
                        perror("shmget fail");
                        exit(-1);
                }
                shmaddr = shmat(shmid, NULL, 0);	//挂载内存到进程空间中
                printf("%s\n", shmaddr);	//打印共享内存中的数据
                shmdt(shmaddr);		//卸载共享内存
                shmctl(shmid, IPC_RMID, NULL);	//删除共享内存
        }

        return 0;
} 

在这里插入图片描述

5. 信号

  • 信号的最大意义不是为了杀死进程,而是实现一些异步通讯的手段。即捕获到指定信号去执行用户自定义函数
  • 特性:简单,但是不能携带大量信息,需要满足某个特定条件才能发送
  1. 编写demo5.c调用信号注册函数signal,当捕获SIGINT或SIGKILL立即执行自定义处理函数handler,如果捕获到SIGUSR1,则忽略该信号
  2. 编写signalHandler.c通过调用程序去发送信号
    demo5.c
#include <signal.h>

typedef void (*sighandler_t)(int); //定义函数指针类型

sighandler_t signal(int signum, sighandler_t handler);

signum 捕获的目标信号     handler 自定义信号处理函数(参数为信号量(int signum))

成功返回的是一个指向某个函数的指针,而这个函数就是上次处理这个信号的信号处理函数,如果未处理过,那就是NULL
失败返回SIG_ERR
//demo5.c
#include <stdio.h>
#include <signal.h>

void handler(int signum)
{
        printf("get signum = %d\n", signum);

        switch(signum)
        {
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
        }
        printf("never quit!\n");
}

int main()
{
        signal(SIGINT, handler);
        signal(SIGKILL, handler);
        signal(SIGUSR1, SIG_IGN);

        while(1);
        return 0;
}

signalHandler.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc, char **argv)
{
        int signum;
        int pid;
        char cmd[20];

        if (argc != 3) {
                printf("sum false!\n");
                exit(-1);
        }
		//方式一
        pid = atoi(argv[2]);
        signum = atoi(argv[1]);
        kill(pid, signum);
		/*方式二
        sprintf(cmd, "kill -%s %s", argv[1], argv[2]);
        system(cmd);
		*/        
        return 0;
}

在这里插入图片描述
高级版信号处理注册函数 sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
   void       (*sa_handler)(int); //信号处理不接受数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理接受数据和sigqueue配合使用,void *为空无数据,不为空有数据
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先设置。
   int        sa_flags;//设置为 SA_SIGINFO 表示接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一
siginfo_t {
     	 int      si_signo;    /* 信号的编号 */
         int      si_errno;    /* 一个errno值 */
         int      si_code;     /* 信号码 */
         int      si_trapno;   /* 导致硬件生成信号(在大多数体系结构上未使用)*/
         pid_t    si_pid;      /* 发送进程ID */
         uid_t    si_uid;      /* 发送进程的实际用户ID */
         int      si_status;   /* 退出值或信号 */
         clock_t  si_utime;    /* 消耗的用户时间 */
         clock_t  si_stime;    /* 消耗的系统时间 */
         sigval_t si_value;    /* 存放信号携带的信息,si_value为共用体与发送信号时的value共用体一致 */
         int      si_int;      /* POSIX.1b信号 */
         void    *si_ptr;      /* POSIX.1b信号 */
         int      si_overrun;  /* 计时器溢出计数;POSIX.1b计时器*/
         int      si_timerid;  /* 计时器ID;POSIX.1b计时器 */
         void    *si_addr;     /* 导致故障的内存位置*/
         long     si_band;     /* band事件(was int in)glibc 2.3.2及更早版本)*/
         int      si_fd;       /* 文件描述符 */
         short    si_addr_lsb; /* 地址的最低有效位(从内核2.6.32开始*/
           }

  • signum:为信号的编号
  • act:不为空则说明自定义信号处理函数
  • oldact:不为空说明备份上一次使用的信号处理函数以便再次调用
  • sa_mask:作用为了保证正在处理一个信号时又捕获这个信号,那么会阻塞到当前信号处理完毕为止。信号处理函数返回后恢复原来所有设置
  • 高级版信号发送函数 sigqueue
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

使用前提

  • 信号处理函数设置为能够接受数据(即使用 sa_sigaction 和配置 sa_flags = SA_SIGINFO)
  • 注意事项:信号携带的信息为字符串的话只能在同一程序中实现。或者使用共享内存配合信号实现
  1. 编写 receive.c 用于接收并处理信号
  2. 编写 send.c 用于发送信号,信号携带一个整型数和一个字符串,由于收发端为不同程序信号无法直接携带字符串,将使用共享内存实现

receive.c

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

void handler(int signum, siginfo_t *info, void *content)
{
        int shmid;
        int key;
        char *shmaddr;
		//创建共享内存
        key = ftok(".", 10);
        shmid = shmget(key, 0, IPC_CREAT | 0666);
        shmaddr = shmat(shmid, NULL, 0);
	
        printf("get signum = %d\n", signum);
		//打印信号携带的信息
        if (content != NULL) {  //判断信号是否有携带信息
                printf("from pid = %d\n", info->si_pid);
                printf("get message character string = %s   int data = %d\n",shmaddr, info->si_value.sival_int);

        }
        shmdt(shmaddr);
        shmctl(shmid, IPC_RMID, NULL);
}

int main()
{
        struct sigaction act;
        struct sigaction oldact;

        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;

        printf("my pid = %d\n", getpid());	
        sigaction(10, &act, &oldact);	

        while(1);
        return 0;
}

send.c

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
        int pid;
        int key;
        int shmid;
        int signum;
        char *shmaddr;
        union sigval value;

        if (argc != 3) {
                printf("sum false!\n");
                exit(-1);
        }
		//创建共享内存
        key = ftok(".", 10);
        shmid = shmget(key, 1024, IPC_CREAT | 0666);
        shmaddr = shmat(shmid, NULL, 0);
        strcpy(shmaddr, "hallo");
        shmdt(shmaddr);
		//发送信号
        pid = atoi(argv[2]);
        signum = atoi(argv[1]);
        printf("my pid = %d\n", getpid());
        value.sival_int = 100;
        sigqueue(pid, signum, value);

        return 0;
}   

在这里插入图片描述

6. 信号量

  • 信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源的同步访问。
  • 一次仅允许一个进程使用的资源称为临界资源(锁),这里的资源可以是一段代码、一个变量或某种硬件资源
  • 如果需要在进程间传递数据需要配合共享内存实现
  • 信号量就是起到钥匙的功能, P操作(拿锁):信号量减1,V操作(放回锁):信号量加1,PV操作可以加减任意正整数

Linux下信号量函数都是在通用信号量数组上进行操作的,不是一个单一的二值信号量进行操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

//创建或获取一个信号量组  成功返回信号量集ID  失败返回-1  errno被设置
int semget(key_t key, int nsems, int semflg);

//控制信号量的相关信息	成功返回非负值  失败返回-1
int semctl(int semid, int semnum, int cmd, ...);

//对信号量组进行操作,改变信号量的值  成功返回0  失败返回-1
int semop(int semid, struct sembuf *sops, size_t nsops);
sops:指向信号量操作结构体指针   nsops: 信号量操作结构体的数量(大于等于1struct sembuf {
		unsigned short sem_num;  // semaphore number
		short          sem_op;   // semaphore operation  (信号量的加减操作)
        short          sem_flg;  // 操作方式设置为SEM_UNDO(当进程终止的时候自动取消对锁的操作) 使用SEM_NOWAIT(无意义)
};

//需要用户自行定义	 通常使用第一个元素 
//为了使用 semctl 的一些控制命令,需要定义一个 union semun。虽然 POSIX 标准中并没有在头文件中定义该联合体,但实际使用时需要手动定义:
union semun {
			int              val;    // Value for SETVAL  (锁的个数) 
            struct semid_ds *buf;    // Buffer for IPC_STAT, IPC_SET 
            unsigned short  *array;  // Array for GETALL, SETALL 
            struct seminfo  *__buf;  // Buffer for IPC_INFO (Linux-specific) 
};

使用信号量配合共享内存、消息队列进行进程间通信
发送端根据用户输入进行发送操作或则退出操作,发送操作:先执行P操作,用户输入需要发送的信息,发送数据,消息队列用于提示接收端读取数据,最后执行V操作,退出操作:使用消息队列向接收端发送信息提示退出,卸载共享内存后退出。 接收端: 去读消息队列里的提示信息(无数据就会堵塞),提示信息为读取数据:执行P操作,然后读取共享内存里的数据并打印,最后执行V操作,若提示消息为退出,卸载共享内存,删除消息队列、共性内存、信号量后退出

//client
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <string.h>

struct msgbuf
{
        long mtype;
        char mtext[128];
};

union semun{
        int              val;
        struct semid_ds *buf;
        unsigned short  *array;
        struct seminfo  *__buf;
};

void seminit(int semid, int num)
{
        union semun sem;
        sem.val = 1;
        semctl(semid, num, SETVAL, sem);
}

void sem_p(int semid, int num)
{
        struct sembuf sbuf;
        sbuf.sem_num = num;
        sbuf.sem_op = -1;
        sbuf.sem_flg = SEM_UNDO;
        semop(semid, &sbuf, 1);
}

void sem_v(int semid, int num)
{
        struct sembuf sbuf;
        sbuf.sem_num = num;
        sbuf.sem_op = 1;
        sbuf.sem_flg = SEM_UNDO;
        semop(semid, &sbuf, 1);
}

void welcome()
{
        printf("----------------------------------------\n");
        printf("--------------welcome to ipc------------\n\n");
        printf("------------input r send datas----------\n");
        printf("---------------input q quit------------\n\n");
        printf("----------------------------------------\n");
}

int main()
{
        int key;
        int shmid,msgid,semid;
        char *shmaddr;
        struct msgbuf r_buf;

        key = ftok(".", 10);

        msgid = msgget(key, IPC_CREAT | 0777);

        shmid = shmget(key, 0, IPC_CREAT | 0666);
        shmaddr = shmat(shmid, NULL, 0);

        semid = semget(key, 1, IPC_CREAT | 0666);

        welcome();

        while(1)
        {
                printf("please input your operation\n");
                memset(r_buf.mtext, '0', sizeof(r_buf.mtext));
                scanf("%s", r_buf.mtext);
                if (strcmp(r_buf.mtext, "r") == 0) {
                        sem_p(semid, 0);
                        printf("plase input you want send datas\n");
                        scanf("%s", shmaddr);
                        r_buf.mtype = 666;
                        msgsnd(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 0);
                        sem_v(semid, 0);
                } else if (strcmp(r_buf.mtext, "q") == 0) {
                        r_buf.mtype = 666;
                        msgsnd(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 0);
                        shmdt(shmaddr);
                        break;
                }
        }

        return 0;
}
//server
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct msgbuf
{
        long mtype;
        char mtext[128];
};

union semun{
        int              val;
        struct semid_ds *buf;
        unsigned short  *array;
        struct seminfo  *__buf;
};

void seminit(int semid, int num)
{
        union semun sem;
        sem.val = 1;
        semctl(semid, num, SETVAL, sem);
}

void sem_p(int semid, int num)
{
        struct sembuf sbuf;
        sbuf.sem_num = num;
        sbuf.sem_op = -1;
        sbuf.sem_flg = SEM_UNDO;
        semop(semid, &sbuf, 1);
}

void sem_v(int semid, int num)
{
        struct sembuf sbuf;
        sbuf.sem_num = num;
        sbuf.sem_op = 1;
        sbuf.sem_flg = SEM_UNDO;
        semop(semid, &sbuf, 1);
}

int main()
{
        int key;
        int shmid,msgid,semid;
        char *shmaddr;
        char *buf;
        struct msgbuf r_buf;

        key = ftok(".", 10);

        msgid = msgget(key, IPC_CREAT | 0777);

        shmid = shmget(key, 1024, IPC_CREAT | 0666);
        shmaddr = shmat(shmid, NULL, 0);

        semid = semget(key, 1, IPC_CREAT | 0666);
        seminit(semid, 0);

        while(1)
        {
                msgrcv(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 666, 0);
                if (strcmp(r_buf.mtext, "r") == 0) {
                        sem_p(semid, 0);
                        printf("%s\n", shmaddr);
                        sem_v(semid, 0);
                } else if (strcmp(r_buf.mtext, "q") == 0) {
                        shmdt(shmaddr);
                        union semun un;
                        shmctl(shmid, IPC_RMID, NULL);
                        msgctl(msgid, IPC_RMID, NULL);
                        semctl(semid, 0, IPC_RMID, un);
                        exit(-1);
                        break;
                }

        }

        return 0;
}
运行结果

服务端
hallo

客户端
----------------------------------------
--------------welcome to ipc------------

------------input r send datas----------
---------------input q quit------------

----------------------------------------
please input your operation
r
plase input you want send datas
hallo
please input your operation
q

7. socket

套接字socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

  • 创建套接字 socket( )
  • 填充服务器网络信息结构体 sockaddr_in
  • 将套接字与服务器网络信息结构体绑定 bind( )
  • 将套接字设置为被动监听模式 listen( )
  • 阻塞等待客户端的连接请求 accept( )
  • 进行通信 recv( )/send( ) (read( )/write( ))
#include <stdio.h> //printf
#include <arpa/inet.h> //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h> //socket bind listen accept connect
#include <netinet/in.h> //sockaddr_in
#include <stdlib.h> //exit
#include <unistd.h> //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{perror(errmsg);\
						  printf("%s -- %s -- %d\n", __FILE__, __func__, __LINE__);\
						  exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd, acceptfd;
	struct sockaddr_in serveraddr, clientaddr;
	socklen_t addrlen = sizeof(serveraddr);
	char buf[N] = {};
	ssize_t bytes;

	if(argc < 3)
	{
		fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器网络信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));

	//第三步:将套接字与服务器网络信息结构体绑定
	if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
	{
		errlog("fail to bind");
	}
	
	//第四步:将套件字设置为被动监听状态 
	if(listen(sockfd, 5) < 0)
	{
		errlog("fail to listen");
	}

	//第五步:阻塞等待客户端的连接请求
	if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) < 0)
	{
		errlog("fail to accept");
	}

	//服务器知道客户端的信息
	printf("%s --> %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

	while(1)
	{
		if((bytes = recv(acceptfd, buf, N, 0)) < 0)
		{
			errlog("fail to recv");
		}
		else if(bytes == 0)
		{
			printf("NO DATA\n");
			exit(1);
		}
		else 
		{
			if(strncmp(buf, "quit", 4) == 0)
			{
				printf("The client is quited\n");
				break;
			}

			printf("client: %s\n", buf);

			strcat(buf, " *_*");
			
			if(send(acceptfd, buf, N, 0) < 0)
			{
				errlog("fail to send");
			}
		}
	}

	close(acceptfd);
	close(sockfd);
	
	return 0;
}

客户端

  • 创建套接字 socket( )
  • 填充服务器网络信息结构体 sockaddr_in
  • 发送客户端的连接请求 connect( )
  • 进行通信 send( )/recv( )
#include <stdio.h> //printf
#include <arpa/inet.h> //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h> //socket bind listen accept connect
#include <netinet/in.h> //sockaddr_in
#include <stdlib.h> //exit
#include <unistd.h> //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{perror(errmsg);\
						  printf("%s -- %s -- %d\n", __FILE__, __func__, __LINE__);\
						  exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	char buf[N] = {};

	if(argc < 3)
	{
		fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器网络信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));

#if 0
	//客户端可以自己指定信息
	struct sockaddr_in clientaddr;
	clientaddr.sin_family = AF_INET;
	clientaddr.sin_addr.s_addr = inet_addr(argv[3]);
	clientaddr.sin_port = htons(atoi(argv[4]));

	if(bind(sockfd, (struct sockaddr *)&clientaddr, addrlen) < 0)
	{
		errlog("fail to bind");
	}
#endif
	//第三步:发送客户端的连接请求
	if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0)
	{
		errlog("fail to connect");
	}

	while(1)
	{
		fgets(buf, N, stdin);
		buf[strlen(buf) - 1] = '\0';

		if(send(sockfd, buf, N, 0) < 0)
		{
			errlog("fail to send");
		}

		if(strncmp(buf, "quit", 4) == 0)
		{
			printf("The client is quited\n");
			break;
		}

		if(recv(sockfd, buf, N, 0) < 0)
		{
			errlog("fail to recv");
		}

		printf("server: %s\n", buf);

	}

	close(sockfd);
	
	return 0;
}

8 总结

  1. 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
  2. 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
  3. 信号: 传递字符串消息只能在同一程序下的进程间
  4. 信号量:不能传递复杂消息,只能用来同步 ,通常配合消息队列、共享内存使用

https://blog.csdn.net/weixin_54178481/article/details/115643530

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值