Communication IPC between Linux system programming processes






Preface

        This article records some of my notes on communication between learning processes, and briefly records the six ways of process communication.
      




1. Communication between processes

  Inter-Process Communication (IPC, InterProcess Communication) refers to the dissemination or exchange of information between different processes. IPC methods usually include pipes (including unnamed pipes and named pipes), message queues, semaphores, shared storage, Sockets, Streams, etc. Among them, Socket and Streams support two process IPC on different hosts.

        Each process has a different user address space. The global variables of any process cannot be seen in another process. Therefore, data exchange between processes must go through the kernel. A buffer is opened in the kernel. Process A transfers the data Copy from user space to the kernel buffer, and process B reads the data from the kernel buffer. This mechanism provided by the kernel is called inter-process communication.

          The nature of communication between different processes: A public resource can be seen between processes; but the form or provider of this resource is different, resulting in different communication methods.

2. Pipeline

        Usually refers to an unnamed pipe, which is the oldest form of IPC in UNIX systems.

1. Features:

  1.  It is half-duplex (i.e. data can only flow in one direction), with fixed read and write ends.

  2. It can only be used for communication between processes that have affinity relationships (also between parent-child processes or sibling processes).

  3. It can be regarded as a special file, and ordinary read, write and other functions can also be used to read and write it. But it is not an ordinary file, does not belong to any other file system, and only exists in memory.

2. Prototype: The pipeline is created by calling the pipe function

#include <unistd.h>
int pipe (int fd[2]);
//返回:成功返回0,出错返回-1     




  The fd parameter returns two file descriptors: fd[0] points to the read end of the parent process, and fd[1] points to the write end of the parent process.

                                                fd[1] points to the write end of the child process, and fd[0] points to the read end of the child process.

3. How pipes implement inter-process communication

        Pipes within a single process are of little use. Therefore, usually the process calling pipe then calls fork, thus creating an IPC channel between the parent process and the child process. As shown below:

   (1) The parent process creates a pipe and gets two file descriptors pointing to both ends of the pipe.

   (2) The parent process forks the child process, and the child process also has two file descriptors pointing to the same pipe.

   (3) The parent process closes fd[0] and the child process closes fd[1], that is, the parent process closes the pipe reading end and the child process closes the pipe writing end (because the pipe only supports one-way communication). The parent process can write to the pipe, and the child process can read from the pipe. The pipe is implemented using a circular queue, and data flows in from the writing end and out from the reading end, thus realizing inter-process communication.

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

int main()
{
	int fd[2];  // 两个文件描述符
	pid_t pid;
	char buff[20];

	if(pipe(fd) < 0)  // 创建管道
		printf("Create Pipe Error!\n");

	if((pid = fork()) < 0)  // 创建子进程
		printf("Fork Error!\n");
	else if(pid > 0)  // 父进程
	{
		close(fd[0]); // 关闭读端
		write(fd[1], "hello world\n", 12);
	}
	else
	{
		close(fd[1]); // 关闭写端
		read(fd[0], buff, 20);
		printf("%s", buff);
	}

	return 0;
}

4. Four situations in which pipes read data

        1. The reading end does not read, and the writing end keeps writing.

        2. The writing end does not write, but the reading end keeps reading.

        3. The reading end keeps reading, and fd[0] remains open, while the writing end writes part of the data and stops writing, and closes fd[1].

        4. The reading end reads part of the data, stops reading and closes fd[0]. The writing end keeps writing and f[1] remains open.

  3. FIFO

        FIFO , also known as named pipe, is a file type.

1. Features

  1. FIFO can exchange data between unrelated processes, unlike unnamed pipes.

  2. A FIFO has a pathname associated with it, and it exists in the file system as a special device file.

2. Prototype


#include <sys/stat.h>
//返回值:成功返回0,出错返回-1
int mkfifo(const char *pathname, mode_t mode);

        The mode parameter openis the same as the mode in the function. Once a FIFO is created, it can be manipulated using normal file I/O functions.

        When opening a FIFO, O_NONBLOCKthe difference is whether the non-blocking flag ( ) is set:

  • If not specified O_NONBLOCK(the default), a read-only open blocks until some other process opens the FIFO for writing. Similarly, a write-only open blocks until some other process opens it for reading.

  • If specified O_NONBLOCK, read-only open returns immediately. A write-only open will return -1 on error. If no process has opened the FIFO for reading, its errno is set to ENXIO.

3. Example

        The communication method of FIFO is similar to using files to transmit data in a process, except that FIFO type files also have the characteristics of pipes. When data is read out, the data is cleared from the FIFO pipeline at the same time and is "first in, first out". The following example demonstrates the process of using FIFO for IPC:

write_fifo.c

#include<stdio.h>
#include<stdlib.h>   // exit
#include<fcntl.h>    // O_WRONLY
#include<sys/stat.h>
#include<time.h>     // time

int main()
{
	int fd;
	int n, i;
	char buf[1024];
	time_t tp;

	printf("I am %d process.\n", getpid()); // 说明进程ID
	
	if((fd = open("fifo1", O_WRONLY)) < 0) // 以写打开一个FIFO 
	{
		perror("Open FIFO Failed");
		exit(1);
	}

	for(i=0; i<10; ++i)
	{
		time(&tp);  // 取系统当前时间
		n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));
		printf("Send message: %s", buf); // 打印
		if(write(fd, buf, n+1) < 0)  // 写入到FIFO中
		{
			perror("Write FIFO Failed");
			close(fd);
			exit(1);
		}
		sleep(1);  // 休眠1秒
	}

	close(fd);  // 关闭FIFO文件
	return 0;
}

read_fifo.c

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>

int main()
{
	int fd;
	int len;
	char buf[1024];

	if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 创建FIFO管道
		perror("Create FIFO Failed");

	if((fd = open("fifo1", O_RDONLY)) < 0)  // 以读打开FIFO
	{
		perror("Open FIFO Failed");
		exit(1);
	}
	
	while((len = read(fd, buf, 1024)) > 0) // 读取FIFO管道
		printf("Read message: %s", buf);

	close(fd);  // 关闭FIFO文件
	return 0;
}

Use gcc to compile and run the above two files in two terminals. You can see the output as follows:

 1 [cheesezh@localhost]$ ./write_fifo 
 2 I am 5954 process.
 3 Send message: Process 5954's time is Mon Apr 20 12:37:28 2015
 4 Send message: Process 5954's time is Mon Apr 20 12:37:29 2015
 5 Send message: Process 5954's time is Mon Apr 20 12:37:30 2015
 6 Send message: Process 5954's time is Mon Apr 20 12:37:31 2015
 7 Send message: Process 5954's time is Mon Apr 20 12:37:32 2015
 8 Send message: Process 5954's time is Mon Apr 20 12:37:33 2015
 9 Send message: Process 5954's time is Mon Apr 20 12:37:34 2015
10 Send message: Process 5954's time is Mon Apr 20 12:37:35 2015
11 Send message: Process 5954's time is Mon Apr 20 12:37:36 2015
12 Send message: Process 5954's time is Mon Apr 20 12:37:37 2015
 1 [cheesezh@localhost]$ ./read_fifo 
 2 Read message: Process 5954's time is Mon Apr 20 12:37:28 2015
 3 Read message: Process 5954's time is Mon Apr 20 12:37:29 2015
 4 Read message: Process 5954's time is Mon Apr 20 12:37:30 2015
 5 Read message: Process 5954's time is Mon Apr 20 12:37:31 2015
 6 Read message: Process 5954's time is Mon Apr 20 12:37:32 2015
 7 Read message: Process 5954's time is Mon Apr 20 12:37:33 2015
 8 Read message: Process 5954's time is Mon Apr 20 12:37:34 2015
 9 Read message: Process 5954's time is Mon Apr 20 12:37:35 2015
10 Read message: Process 5954's time is Mon Apr 20 12:37:36 2015
11 Read message: Process 5954's time is Mon Apr 20 12:37:37 2015

        The above example can be extended to an example of client process-server process communication. write_fifoIts function is similar to that of a client. It can open multiple clients to send request information to a server. read_fifoSimilar to a server, it monitors the reading end of the FIFO in a timely manner. When there is data At the same time, it is read and processed, but a key issue is that each client must know the FIFO interface provided by the server in advance. The following figure shows this arrangement:

 3. Message queue

1. Features

  1. Message queues are record-oriented, where messages have a specific format and a specific priority.

  2. The message queue is independent of the sending and receiving processes. When the process terminates, the message queue and its contents are not deleted.

  3. The message queue can realize random query of messages. The messages do not have to be read in first-in, first-out order, but can also be read according to the type of message.

  4. The message queue is independent of the sending and receiving processes. When the process is terminated, the message queue and its contents will not be deleted.

2. Prototype

#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msggetA new message queue will be created in the following two situations :

  • If there is no message queue corresponding to the key value key, and the flag contains IPC_CREATthe flag bit, the queue ID is returned successfully, and -1 is returned on failure.
  • The key parameter is IPC_PRIVATE.

When the function msgrcvreads the message queue, the type parameter has the following situations:

  • type == 0, returns the first message in the queue;
  • type > 0, returns the first message in the queue with message type type;
  • type < 0, returns the messages in the queue whose message type value is less than or equal to the absolute value of type. If there are multiple messages, the message with the smallest type value is taken.

        It can be seen that when the type value is non-0, it is used to read messages in non-first-in-first-out order. Type can also be regarded as the weight of priority.

3. Example

        A simple example of using message queue for IPC is written below. The server program has been waiting for a specific type of message. After receiving this type of message, it sends another specific type of message as feedback, and the client reads the message. Give feedback and print it out.

msg_server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "/etc/passwd"

// 消息结构
struct msg_form {
	long mtype;
	char mtext[256];
};

int main()
{
	int msqid;
	key_t key;
	struct msg_form msg;
	
	// 获取key值
	if((key = ftok(MSG_FILE,'z')) < 0)
	{
		perror("ftok error");
		exit(1);
	}

	// 打印key值
	printf("Message Queue - Server key is: %d.\n", key);

	// 创建消息队列
	if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
	{
		perror("msgget error");
		exit(1);
	}

	// 打印消息队列ID及进程ID
	printf("My msqid is: %d.\n", msqid);
	printf("My pid is: %d.\n", getpid());

	// 循环读取消息
	for(;;) 
	{
		msgrcv(msqid, &msg, 256, 888, 0);// 返回类型为888的第一个消息
		printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
		printf("Server: receive msg.mtype is: %d.\n", msg.mtype);

		msg.mtype = 999; // 客户端接收的消息类型
		sprintf(msg.mtext, "hello, I'm server %d", getpid());
		msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
	}
	return 0;
}

msg_client.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "/etc/passwd"

// 消息结构
struct msg_form {
	long mtype;
	char mtext[256];
};

int main()
{
	int msqid;
	key_t key;
	struct msg_form msg;

	// 获取key值
	if ((key = ftok(MSG_FILE, 'z')) < 0) 
	{
		perror("ftok error");
		exit(1);
	}

	// 打印key值
	printf("Message Queue - Client key is: %d.\n", key);

	// 打开消息队列
	if ((msqid = msgget(key, IPC_CREAT|0777)) == -1) 
	{
		perror("msgget error");
		exit(1);
	}

	// 打印消息队列ID及进程ID
    printf("My msqid is: %d.\n", msqid);
	printf("My pid is: %d.\n", getpid());

	// 添加消息,类型为888
	msg.mtype = 888;
	sprintf(msg.mtext, "hello, I'm client %d", getpid());
	msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

	// 读取类型为999的消息
	msgrcv(msqid, &msg, 256, 999, 0);
	printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
	printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
	return 0;
}

4. Semaphore

        The semaphore is different from the IPC structure already introduced. It is a counter. Semaphores are used to implement mutual exclusion and synchronization between processes, rather than storing inter-process communication data.

1. Features

  1. Semaphore is used for inter-process synchronization. To transfer data between processes, it needs to be combined with shared memory .

  2. The semaphore is based on the PV operation of the operating system, and the program's operations on the semaphore are all atomic operations.

  3. Each PV operation on the semaphore is not limited to adding 1 or subtracting 1 to the semaphore value, but can also add or subtract any positive integer.

  4. Supports semaphore groups.

2. Prototype

        The simplest semaphore is a variable that can only take 0 and 1. This is also the most common form of semaphore, called binary semaphore (Binary Semaphore) . A semaphore that can take multiple positive integers is called a universal semaphore.

        The semaphore functions under Linux all operate on a general semaphore array, rather than a single binary semaphore.

#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

        When semgetcreating a new semaphore collection, the number of semaphores in the collection (ie num_sems) must be specified, usually 1; if an existing collection is referenced, num_sems0 will be specified.

In semopthe function, sembufthe structure is defined as follows:

struct sembuf 
{
    short sem_num; // 信号量组中对应的序号,0~sem_nums-1
    short sem_op;  // 信号量值在一次操作中的改变量
    short sem_flg; // IPC_NOWAIT, SEM_UNDO
}

where sem_op is the change amount of the semaphore in one operation:

  • If sem_op > 0, it means that the process releases the corresponding number of resources, and the value of sem_op is added to the value of the semaphore. If there are processes sleeping waiting for this semaphore, wrap them.

  • If so sem_op < 0, request the resource with the absolute value of sem_op.

    • If the corresponding number of resources can satisfy the request, the absolute value of sem_op is subtracted from the value of the semaphore, and the function returns successfully.
    • This operation is relevant when the corresponding number of resources cannot satisfy the request sem_flg.
      • If sem_flg is specified IPC_NOWAIT, the semop function returns an error EAGAIN.
      • If sem_flg is not specified IPC_NOWAIT, the semncnt value of the semaphore is increased by 1, and then the process hangs until the following occurs:
        • When the corresponding number of resources can satisfy the request, the semncnt value of this semaphore is reduced by 1, and the value of the semaphore is reduced by the absolute value of sem_op. Return successfully;
        • The semaphore is deleted, and the function smeop returns EIDRM when an error occurs;
        • The process captures the signal and returns from the signal processing function. In this case, the semncnt value of the semaphore is reduced by 1, and the function semop returns EINTR when an error occurs.
  • If sem_op == 0, the process blocks until the corresponding value of the semaphore is 0:
    • When the semaphore is already 0, the function returns immediately.
    • If the value of the semaphore is not 0, sem_flgthe function action is determined according to:
      • If sem_flg is specified IPC_NOWAIT, an error will be returned EAGAIN.
      • If sem_flg is not specified IPC_NOWAIT, the semncnt value of the semaphore is increased by 1, and then the process hangs until the following occurs:
        • The semaphore value is 0, decrement the semzcnt value of the semaphore by 1, and the function semop returns successfully;
        • The semaphore is deleted, and the function smeop returns EIDRM when an error occurs;
        • The process captures the signal and returns from the signal processing function. In this case, the semncnt value of the semaphore is reduced by 1, and the function semop returns EINTR when an error occurs.
  • There are many kinds of commands in semctlfunctions. Here are two commonly used ones:
    • SETVAL: Used to initialize the semaphore to a known value. The required value is passed as the val member of the union semun. The semaphore needs to be set up before it is used for the first time.
    • IPC_RMID: Delete a semaphore collection. If you don't delete the semaphore, it will continue to exist in the system even if the program has exited, which may cause problems the next time you run the program, and the semaphore is a limited resource.

3. Example

#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>

// 联合体,用于semctl初始化
union semun
{
	int              val; /*for SETVAL*/
	struct semid_ds *buf;
	unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
	union semun tmp;
	tmp.val = value;
	if(semctl(sem_id, 0, SETVAL, tmp) == -1)
	{
		perror("Init Semaphore Error");
		return -1;
	}
	return 0;
}

// P操作:
//	若信号量值为1,获取资源并将信号量值-1 
//	若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
	struct sembuf sbuf;
	sbuf.sem_num = 0; /*序号*/
	sbuf.sem_op = -1; /*P操作*/
	sbuf.sem_flg = SEM_UNDO;

	if(semop(sem_id, &sbuf, 1) == -1)
	{
		perror("P operation Error");
		return -1;
	}
	return 0;
}

// V操作:
//	释放资源并将信号量值+1
//	如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
	struct sembuf sbuf;
	sbuf.sem_num = 0; /*序号*/
	sbuf.sem_op = 1;  /*V操作*/
	sbuf.sem_flg = SEM_UNDO;

	if(semop(sem_id, &sbuf, 1) == -1)
	{
		perror("V operation Error");
		return -1;
	}
	return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
	union semun tmp;
	if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
	{
		perror("Delete Semaphore Error");
		return -1;
	}
	return 0;
}


int main()
{
	int sem_id;  // 信号量集ID
	key_t key;  
	pid_t pid;

	// 获取key值
	if((key = ftok(".", 'z')) < 0)
	{
		perror("ftok error");
		exit(1);
	}

	// 创建信号量集,其中只有一个信号量
	if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
	{
		perror("semget error");
		exit(1);
	}

	// 初始化:初值设为0资源被占用
	init_sem(sem_id, 0);

	if((pid = fork()) == -1)
		perror("Fork Error");
	else if(pid == 0) /*子进程*/ 
	{
		sleep(2);
		printf("Process child: pid=%d\n", getpid());
		sem_v(sem_id);  /*释放资源*/
	}
	else  /*父进程*/
	{
		sem_p(sem_id);   /*等待资源*/
		printf("Process father: pid=%d\n", getpid());
		sem_v(sem_id);   /*释放资源*/
		del_sem(sem_id); /*删除信号量集*/
	}
	return 0;
}

        If the above example does not add a semaphore, the parent process will complete execution first. A semaphore is added here to let the parent process wait for the child process to finish executing before executing it.

5. Shared memory

Shared Memory refers to two or more processes sharing a given storage area.

Roughly follow these steps: create/open memory, map memory (connection), data, release memory (disconnect), clear sharing

1. Features

  1. Shared memory is the fastest type of IPC because the process accesses the memory directly.

  2. Because multiple processes can operate simultaneously, synchronization is required.

  3. Semaphores + shared memory are usually used together. Semaphores are used to synchronize access to shared memory.

2. Prototype

#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

When using shmgeta function to create a section of shared memory, its size must be specified; and if an existing shared memory is referenced, the size must be specified as 0.

When using shmgeta function to create a section of shared memory, its size must be specified; and if an existing shared memory is referenced, the size must be specified as 0.

When a section of shared memory is created, it cannot be accessed by any process. You must use shmata function to connect the shared memory to the address space of the current process. After the connection is successful, the shared memory area object is mapped to the address space of the calling process, and can then be accessed like local space.

shmdtFunction is used to disconnect shmatthe established connection. Note that this does not delete the shared memory from the system, it is just that the current process can no longer access the shared memory.

shmctlThe function can perform various operations on shared memory and perform corresponding operations according to the parameter cmd. Commonly used is IPC_RMID(remove the shared memory from the system).

3. Example

In this example, a combination of [shared memory + semaphore + message queue] is used to implement communication between the server process and the client process.

  • Shared memory is used to transfer data;
  • Semaphores are used for synchronization;
  • The message queue is used to notify the server to read after the client modifies the shared memory.

Server.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h>  // shared memory
#include<sys/sem.h>  // semaphore
#include<sys/msg.h>  // message queue
#include<string.h>   // memcpy

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P操作:
//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}

// 创建一个信号量集
int creat_sem(key_t key)
{
	int sem_id;
	if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
	{
		perror("semget error");
		exit(-1);
	}
	init_sem(sem_id, 1);  /*初值设为1资源未占用*/
	return sem_id;
}


int main()
{
	key_t key;
	int shmid, semid, msqid;
	char *shm;
	char data[] = "this is server";
	struct shmid_ds buf1;  /*用于删除共享内存*/
	struct msqid_ds buf2;  /*用于删除消息队列*/
	struct msg_form msg;  /*消息队列用于通知对方更新了共享内存*/

	// 获取key值
	if((key = ftok(".", 'z')) < 0)
	{
		perror("ftok error");
		exit(1);
	}

	// 创建共享内存
	if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
	{
		perror("Create Shared Memory Error");
		exit(1);
	}

	// 连接共享内存
	shm = (char*)shmat(shmid, 0, 0);
	if((int)shm == -1)
	{
		perror("Attach Shared Memory Error");
		exit(1);
	}


	// 创建消息队列
	if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
	{
		perror("msgget error");
		exit(1);
	}

	// 创建信号量
	semid = creat_sem(key);
	
	// 读数据
	while(1)
	{
		msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
		if(msg.mtext == 'q')  /*quit - 跳出循环*/ 
			break;
		if(msg.mtext == 'r')  /*read - 读共享内存*/
		{
			sem_p(semid);
			printf("%s\n",shm);
			sem_v(semid);
		}
	}

	// 断开连接
	shmdt(shm);

    /*删除共享内存、消息队列、信号量*/
	shmctl(shmid, IPC_RMID, &buf1);
	msgctl(msqid, IPC_RMID, &buf2);
	del_sem(semid);
	return 0;
}

Client.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h>  // shared memory
#include<sys/sem.h>  // semaphore
#include<sys/msg.h>  // message queue
#include<string.h>   // memcpy

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// P操作:
//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}


int main()
{
	key_t key;
	int shmid, semid, msqid;
	char *shm;
	struct msg_form msg;
	int flag = 1; /*while循环条件*/

	// 获取key值
	if((key = ftok(".", 'z')) < 0)
	{
		perror("ftok error");
		exit(1);
	}

	// 获取共享内存
	if((shmid = shmget(key, 1024, 0)) == -1)
	{
		perror("shmget error");
		exit(1);
	}

	// 连接共享内存
	shm = (char*)shmat(shmid, 0, 0);
	if((int)shm == -1)
	{
		perror("Attach Shared Memory Error");
		exit(1);
	}

	// 创建消息队列
	if ((msqid = msgget(key, 0)) == -1)
	{
		perror("msgget error");
		exit(1);
	}

	// 获取信号量
	if((semid = semget(key, 0, 0)) == -1)
	{
		perror("semget error");
		exit(1);
	}
	
	// 写数据
	printf("***************************************\n");
	printf("*                 IPC                 *\n");
	printf("*    Input r to send data to server.  *\n");
	printf("*    Input q to quit.                 *\n");
	printf("***************************************\n");
	
	while(flag)
	{
		char c;
		printf("Please input command: ");
		scanf("%c", &c);
		switch(c)
		{
			case 'r':
				printf("Data to send: ");
				sem_p(semid);  /*访问资源*/
				scanf("%s", shm);
				sem_v(semid);  /*释放资源*/
				/*清空标准输入缓冲区*/
				while((c=getchar())!='\n' && c!=EOF);
				msg.mtype = 888;  
				msg.mtext = 'r';  /*发送消息通知服务器读数据*/
				msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
				break;
			case 'q':
				msg.mtype = 888;
				msg.mtext = 'q';
				msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
				flag = 0;
				break;
			default:
				printf("Wrong input!\n");
				/*清空标准输入缓冲区*/
				while((c=getchar())!='\n' && c!=EOF);
		}
	}

	// 断开连接
	shmdt(shm);

	return 0;
}

Note: When scanf()characters or strings are entered, they are left in the buffer \n, so the standard input buffer needs to be cleared after each input operation. But since the gcc compiler doesn't support it fflush(stdin)(it's just an extension to standard C), we used an alternative:

while((c=getchar())!='\n' && c!=EOF);




Summarize

Summary of the five communication methods:

1. Pipeline: slow, limited capacity, only parent and child processes can communicate.    

2.FIFO: Any process can communicate, but the speed is slow.    

3. Message Queue: The capacity is limited by the system, and attention should be paid to the problem that the data was not read last time when reading for the first time.    

4. Semaphore: It cannot transmit complex messages and can only be used for synchronization.    

5. Shared memory area: The capacity can be easily controlled and the speed is fast, but synchronization must be maintained. For example, when one process is writing, another process must pay attention to reading and writing issues, which is equivalent to thread safety in threads. Of course, shared memory Areas can also be used for communication between threads, but this is not necessary. Threads already share a piece of memory in the same process.

Reference: https://blog.csdn.net/skyroben/article/details/71513385

        http://songlee24.github.io/2015/04/21/linux-IPC/

Guess you like

Origin blog.csdn.net/qq_44848795/article/details/122016633