Pipes、FIFOs和UNIX Domain Sockets

directionduplexrelationshipintermachine
pipe unidirectionalhalf-duplexparent–childno
FIFObidirectionalhalf-duplexunrelatedno
socketpairbidirectionalfull-duplexparent–childno
named UNIX domain socketbidirectionalfull-duplexunrelatedno
  • 管道分为哪些类型?

Ordinary pipes and named pipes.

Ordinary pipes on Windows systems are termed anonymous pipes.

Named pipes are referred to as FIFOs in UNIX systems. 

  • 为什么叫匿名管道与命名管道?为什么叫FIFOs?

匿名管道通过接口int pipe(int fd[2])创建,没有参数指定管道的名字;命名管道通过接口int mkfifo(const char *path, mode_t mode);创建,path指定了管道的名字。
FIFOs:first in first out。

  • 普通管道与命名管道有什么差异?

Once a named pipe is established, several processes can use it for communication. In fact, in a typical scenario, a named pipe has several writers. Additionally, named pipes continue to exist after communicating processes have finished. 

Once created, named pipes appear as typical files in the file system. It will continue to exist until it is explicitly deleted from the file system.

But whereas pipes can be used only for linear connections between processes, a FIFO has a name, so it can be used for nonlinear connections.

  • UNIX Domain Sockets分为哪些类型?

UNIX domain sockets are like a cross between sockets and pipes. You can use the network-oriented socket interfaces with them, or you can use the socketpair function to create a pair of unnamed, connected, UNIX domain sockets.

与管道类似,UNIX Domain Sockets也分为匿名和命名两种。

  • UNIX Domain Sockets与Internet domain sockets有什么差异?

UNIX domain sockets are more efficient.UNIX domain sockets only copy data; they have no protocol processing to perform, no network headers to add or remove, no checksums to calculate, no sequence numbers to generate, and no acknowledgements to send.

The address format used with UNIX domain sockets differs from that used with Internet domain sockets.

When we bind an address to a UNIX domain socket, the system creates a file of type S_IFSOCK with the same name.

  • Pipes、FIFOs和UNIX Domain Sockets要实现进程间通信,找到对端进程的关键点是什么?

pipe: Typically, a parent process creates a pipe and uses it to communicate with a child process that it creates via fork(). A child process inherits open files from its parent. Since a pipe is a special type of file, the child inherits the pipe from its parent process.

FIFO:两个通信的进程知道创建的FIFO的路径,通过向FIFO读写数据进行通信。

socketpair:与Ordinary pipes类似。

Naming UNIX Domain Sockets:服务器进程和客户端进程都知道一个地址。服务器进程绑定这个地址到socket,并侦听该socket;客户端进程向该地址发起连接。

  • Does the pipe/FIFO/UNIX domain socket allow bidirectional communication, or is communication unidirectional?

pipe: Pipes are unidirectional, allowing only one-way communication. If two-way communication is required, two pipes must be used, with each pipe sending data in a different direction.

FIFO: Communication can be bidirectional.

UNIX domain socket: UNIX domain sockets are bidirectional.

  • If two-way communication is allowed, is it half duplex (data can travel only one way at a time) or full duplex (data can travel in both directions at the same time)?

pipe: Pipes are half duplex (i.e., data flows in only one direction).

FIFO:  Although FIFOs allow bidirectional communication, only half-duplex transmission is permitted. If data must travel in both directions, two FIFOs are typically used.

UNIX domain sockets: UNIX domain sockets are full-duplex by default.

  • Must a relationship (such as parent–child) exist between the communicating processes?

pipe: Ordinary pipes require a parent–child relationship between the communicating processes.

FIFO: No parent–child relationship is required, unrelated processes can exchange data.

socketpair: socketpair can’t be addressed by unrelated processes.

named UNIX domain socket: unrelated processes.

  • Can the pipes/FIFOs/UNIX domain sockets communicate over a network, or must the communicating processes reside on the same machine?

pipe: Ordinary pipes can be used only for communication between processes on the same machine.

FIFO: The communicating processes must reside on the same machine.

UNIX domain sockets: UNIX domain sockets are used to communicate with processes running on the same machine.

  • 使用Pipes、FIFOs和UNIX Domain Sockets实现进程间通信的示例程序。

Pipes与socketpair示例程序参见:进程通信:管道(pipe)和socketpair区别

There are two uses for FIFOs.
1. FIFOs are used by shell commands to pass data from one shell pipeline to another without creating intermediate temporary files.
2. FIFOs are used as rendezvous points in client–server applications to pass data between the clients and the servers.

第一种情况详述见APUE P554。程序示例:

infile:

11111
22222
33333

prog1.cpp:

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

#define BUFSIZE 1024

int main()
{
    int n = 0;
    char buf[BUFSIZE] = {0};
	
    while ((n = read(STDIN_FILENO, buf, BUFSIZE)) > 0)
	{
		if ((write(STDOUT_FILENO, buf, n)) != n)
		{
            printf("write error.\n");
            return -1;
        }
	}
	
    if (n < 0)
	{
        printf("read error.\n");
        return -1;
    }
	
    return 0;
}

 prog2.cpp:

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

#define BUFSIZE 1024

int main()
{
	int fd = -1;
	int n = 0;
    char buf[BUFSIZE] = {0};
	
	fd = open("outfile2", O_RDWR|O_CREAT, 0664);
	if (fd < 0)
	{
		printf("open error.\n");
        return -1;
	}
	
    while ((n = read(STDIN_FILENO, buf, BUFSIZE)) > 0)
	{
		if ((write(fd, buf, n)) != n)
		{
            printf("write error.\n");
            return -1;
        }
	}
	
    if (n < 0)
	{
        printf("read error.\n");
        return -1;
    }
	
    return 0;
}

prog3.cpp将prog2.cpp中的outfile2改为outfile3,其余与prog2.cpp相同。

 执行命令得到输出结果:

gavin@gavin-vm:/var/tmp$ g++ prog1.cpp -o prog1
gavin@gavin-vm:/var/tmp$ g++ prog2.cpp -o prog2
gavin@gavin-vm:/var/tmp$ g++ prog3.cpp -o prog3
gavin@gavin-vm:/var/tmp$ mkfifo fifo1
gavin@gavin-vm:/var/tmp$ prog3 < fifo1 &
[1] 5776
gavin@gavin-vm:/var/tmp$ prog1 < infile | tee fifo1 | prog2
[1]+  Done                    prog3 < fifo1
gavin@gavin-vm:/var/tmp$ cat outfile2
11111
22222
33333
gavin@gavin-vm:/var/tmp$ cat outfile3
11111
22222
33333

第二种情况详述见APUE Figure 15.22。代码示例(参考FIFO的用途):

// server.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#define FIFO_PATH "/var/tmp/fifo"
#define BUFSIZE 64

int fifo_exists(const char *path)
{
	return !access(path, F_OK);
}

int main()
{
	int fd = -1;
	int rval = 0;
	int err = 0;
	int do_close = 0;
	char read_msg[BUFSIZE] = {0};
	
	if (fifo_exists(FIFO_PATH))
	{
		unlink(FIFO_PATH);
	}
	
	if (mkfifo(FIFO_PATH, 0664) < 0)
	{
		printf("mkfifo error\n");
		return -1;
	}
	
	// 阻塞, 直到有进程以写方式打开。
	fd = open(FIFO_PATH, O_RDONLY);
	if (fd < 0)
	{
		printf("open error\n");
		rval = -2;
		goto errout;
	}
	
	while (1)
	{	
		memset(read_msg, 0, sizeof(read_msg));
		if (read(fd, read_msg, BUFSIZE) > 0)
		{
			read_msg[BUFSIZE-1] = '\0';
			printf("read: %s\n", read_msg);
		}
		else
		{
			printf("read error\n");
			rval = -3;
			do_close = 1;
			goto errout;
		}
	}
	
	return 0;

errout:
	err = errno;
	if (1 == do_close)
	{
		close(fd);
	}
	unlink(FIFO_PATH);	
	errno = err;
	return rval;
}
// client.c (运行多个客户端来表示多个客户端进程)
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <errno.h>

#define FIFO_PATH "/var/tmp/fifo"
#define BUFSIZE 64

int fifo_exists(const char *path)
{
	return !access(path, F_OK);
}

int main()
{
	int fd = -1;
	int rval = 0;
	int err = 0;
	char write_msg[BUFSIZE] = {0};
	
	assert(1 == fifo_exists(FIFO_PATH));
	
	// 阻塞, 直到有进程以读方式打开。
	fd = open(FIFO_PATH, O_WRONLY);
	if (fd < 0)
	{
		printf("open error\n");
		return -1;
	}
	
	while (1)
	{
		memset(write_msg, 0, sizeof(write_msg));
		std::cin.getline(write_msg, sizeof(write_msg));
		if (write(fd, write_msg, strlen(write_msg)+1) < 0)
		{
			printf("write error\n");
			rval = -2;
			goto errout;
		}
	}
	
	return 0;

errout:
	err = errno;
	close(fd);
	errno = err;
	return rval;
}

第一个终端执行./server: 

gavin@gavin-vm:/var/tmp$ g++ client.cpp -o client
gavin@gavin-vm:/var/tmp$ g++ server.cpp -o server
gavin@gavin-vm:/var/tmp$ ./server

第二个终端执行./client并输入Hello, world!回车:

gavin@gavin-vm:/var/tmp$ ./client
Hello, world!

 第一个终端输出:

read: Hello, world!

对于int open(const char *pathname, int flags)中,flags为O_RDWR的说明:

“This implementation will NEVER block on a O_RDWR open, since the process can at least talk to itself.
相当于一个进程同时打开命名管道的两端,所以不需要等待,只要任何一端是第一次打开,就唤醒在睡眠的进程。”

摘自linux内核情景分析之命名管道

Namd UNIX Domain Sockets示例程序:

/* Unique_Connections_server.cpp */
#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <errno.h>

#define QLEN 10

/*
* Create a server endpoint of a connection.
* Returns fd if all OK, <0 on error.
*/
int
serv_listen(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;
	
	if (strlen(name) >= sizeof(un.sun_path)) {
		errno = ENAMETOOLONG;
		return(-1);
	}
	
	/* create a UNIX domain stream socket */
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-2);
	
	unlink(name); /* in case it already exists */
	
	/* fill in socket address structure */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	
	/* bind the name to the descriptor */
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -3;
		goto errout;
	}
	
	if (listen(fd, QLEN) < 0) { /* tell kernel we’re a server */
		rval = -4;
		goto errout;
	}
	return(fd);
	
errout:
	err = errno;
	close(fd);
	errno = err;
	return(rval);
}

#define STALE 30 /* client’s name can’t be older than this (sec) */

/*
* Wait for a client connection to arrive, and accept it.
* We also obtain the client’s user ID from the pathname
* that it must bind before calling us.
* Returns new fd if all OK, <0 on error
*/
int
serv_accept(int listenfd, uid_t *uidptr)
{
	int clifd, err, rval;
	socklen_t len;
	time_t staletime;
	struct sockaddr_un un;
	struct stat statbuf;
	char *name;
	
	/* allocate enough space for longest name plus terminating null */
	if ((name = (char *)malloc(sizeof(un.sun_path) + 1)) == NULL)
		return(-1);
	len = sizeof(un);
	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
		free(name);
		return(-2); /* often errno=EINTR, if signal caught */
	}
	
	/* obtain the client’s uid from its calling address */
	len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
	memcpy(name, un.sun_path, len);
	name[len] = 0; /* null terminate */
	if (stat(name, &statbuf) < 0) {
		rval = -3;
		goto errout;
	}
	
#ifdef S_ISSOCK /* not defined for SVR4 */
	if (S_ISSOCK(statbuf.st_mode) == 0) {
		rval = -4; /* not a socket */
		goto errout;
	}
#endif
	
	if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
		(statbuf.st_mode & S_IRWXU) != S_IRWXU) {
		rval = -5; /* is not rwx------ */
		goto errout;
	}
	
	staletime = time(NULL) - STALE;
	if (statbuf.st_atime < staletime ||
		statbuf.st_ctime < staletime ||
		statbuf.st_mtime < staletime) {
		rval = -6; /* i-node is too old */
		goto errout;
	}
	
	if (uidptr != NULL)
		*uidptr = statbuf.st_uid; /* return uid of caller */
	unlink(name); /* we’re done with pathname now */
	free(name);
	return(clifd);
	
errout:
	err = errno;
	close(clifd);
	free(name);
	errno = err;
	return(rval);
}

#define CLI_PATH_SERVER "/var/tmp/server.socket"
#define BUFSIZE 1024

int main()
{
	int listenfd = -1;
	int clifd = -1;
	int err = 0;
	int rval = 0;
	int do_close = 0;
	uid_t uidptr = -1;
	char sndbuf[BUFSIZE] = "Test Unique Connections at server.";
	char rcvbuf[BUFSIZE] = {0};
	ssize_t sndbytes = 0;
	ssize_t rcvbytes = 0;
	
	listenfd = serv_listen(CLI_PATH_SERVER);
	if (listenfd < 0) {
		fprintf(stderr, "serv_listen failed\n");
		return -1;
	}
	
	clifd = serv_accept(listenfd, &uidptr);
	if (clifd < 0) {
		fprintf(stderr, "serv_accept failed\n");
		rval = -2;
		goto errout;
	}
	
	rcvbytes = recv(clifd, rcvbuf, sizeof(rcvbuf), 0);
	if (rcvbytes < 0) {
		fprintf(stderr, "recv failed\n");
		rval = -3;
		do_close = 1;
		goto errout;
	}
	printf("Server received: %s\n", rcvbuf);
	
	sndbytes = send(clifd, sndbuf, sizeof(sndbuf), 0);
	if (sndbytes < 0) {
		fprintf(stderr, "send failed\n");
		rval = -4;
		do_close = 1;
		goto errout;
	}
	
	return 0;

errout:
	err = errno;
	if (1 == do_close)
	{
		close(clifd);
	}
	close(listenfd);
	errno = err;
	return(rval);
}
/* Unique_Connections_client.cpp */
#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define CLI_PATH "/var/tmp/"
#define CLI_PERM S_IRWXU /* rwx for user only */

/*
* Create a client endpoint and connect to a server.
* Returns fd if all OK, <0 on error.
*/
int
cli_conn(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un, sun;
	int do_unlink = 0;
	
	if (strlen(name) >= sizeof(sun.sun_path)) {
		errno = ENAMETOOLONG;
		return(-1);
	}
	
	/* create a UNIX domain stream socket */
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-1);
	
	/* fill socket address structure with our address */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	sprintf(un.sun_path, "%s%05ld", CLI_PATH, (long)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;
		goto errout;
	}
	if (chmod(un.sun_path, CLI_PERM) < 0) {
		rval = -3;
		do_unlink = 1;
		goto errout;
	}
	
	/* fill socket address structure with server’s address */
	memset(&sun, 0, sizeof(sun));
	sun.sun_family = AF_UNIX;
	strcpy(sun.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	if (connect(fd, (struct sockaddr *)&sun, len) < 0) {
		rval = -4;
		do_unlink = 1;
		goto errout;
	}
	return(fd);
	
errout:
	err = errno;
	close(fd);
	if (do_unlink)
		unlink(un.sun_path);
	errno = err;
	return(rval);
}

#define CLI_PATH_SERVER "/var/tmp/server.socket"
#define BUFSIZE 1024

int main()
{
	int fd = -1;
	int err = 0;
	int rval = 0;
	char sndbuf[BUFSIZE] = "Test Unique Connections at client.";
	char rcvbuf[BUFSIZE] = {0};
	ssize_t sndbytes = 0;
	ssize_t rcvbytes = 0;
	
	fd = cli_conn(CLI_PATH_SERVER);
	if (fd < 0) {
		fprintf(stderr, "cli_conn failed\n");
		return -1;
	}
	
	sndbytes = send(fd, sndbuf, sizeof(sndbuf), 0);
	if (sndbytes < 0) {
		fprintf(stderr, "send failed\n");
		rval = -2;
		goto errout;
	}
	
	rcvbytes = recv(fd, rcvbuf, sizeof(rcvbuf), 0);
	if (rcvbytes < 0) {
		fprintf(stderr, "recv failed\n");
		rval = -3;
		goto errout;
	}
	printf("Client received: %s\n", rcvbuf);
	
	return 0;

errout:
	err = errno;
	close(fd);
	errno = err;
	return(rval);
}
gavin@gavin-vm:/mnt/hgfs/shared_folder$ g++ Unique_Connections_server.cpp -o Unique_Connections_server
gavin@gavin-vm:/mnt/hgfs/shared_folder$ ./Unique_Connections_server
Server received: Test Unique Connections at client.
gavin@gavin-vm:/mnt/hgfs/shared_folder$ g++ Unique_Connections_client.cpp -o Unique_Connections_client
gavin@gavin-vm:/mnt/hgfs/shared_folder$ ./Unique_Connections_client
Client received: Test Unique Connections at server.

Message Queues

We don’t have to fetch the messages in a first-in, first-out order. Instead, we can fetch messages based on their type field.
 

  • 子进程能从父进程继承到什么?

The new process consists of a copy of the address space of the original process. This mechanism allows the parent process to communicate easily with its child process(ordinary pipe).

The child process inherits privileges and scheduling attributes from the parent, as well certain resources, such as open files.

Because the child is a copy of the parent, each process has its own copy of any data.

  • 子进程不能从父进程继承什么?
  • 一个进程的入口函数是main函数,fork出的子进程如何进入main函数?

After a fork() system call, one of the two processes typically uses the exec() system call to replace the process’s memory space with a new program. The exec() system call loads a binary file into memory (destroying the memory image of the program containing the exec() system call) and starts its execution. In this manner, the two processes are able to communicate and then go their separate ways.

  • 进程如何退出?
  • 线程如何退出?
  • 有了进程为什么还要有线程?
  • 进程和线程有什么区别?
  • 进程间如何通信?
  • 线程间如何通信?
  • 进程分用户进程和内核进程吗?
  • 如何区分用户态和内核态?
  • 系统的原始进程是什么?

UNIX: init (also known as System V init) is assigned a pid of 1

Linux: systemd

  • 进程需要哪些资源?

CPU time, memory, files, I/O devices

  • 进程的资源从哪里获得?

A child process may be able to obtain its resources directly from the operating system, or it may be constrained to a subset of the resources of the parent process. The parent may have to partition its resources among its children, or it may be able to share some resources (such as memory or files) among several of its children. Restricting a child process to a subset of the parent’s resources prevents any process from overloading the system by creating too many child processes.

When a process creates a new process, two possibilities for execution exist:
1. The parent continues to execute concurrently with its children.
2. The parent waits until some or all of its children have terminated.
There are also two address-space possibilities for the new process:
1. The child process is a duplicate of the parent process (it has the same program and data as the parent).
2. The child process has a new program loaded into it.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值