Linux进程间通信(Linux IPC)

目录

    • 前言
    • 1、匿名管道(PIPE)
    • 2、命名管道(FIFO)
    • 3、信号
    • 4、共享内存映射
    • 5、共享内存
    • 6、消息队列
    • 7、信号量
    • 8、UNIX域套接字(Unix Domain Socket)
    • socketpair

前言

        Linux环境下,进程地址空间相互独立、彼此隔离,因此进程间的数据不能之间访问。如果要交换数据,必须要通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷贝到内核缓冲区,进程B再把数据从内核缓冲区拷贝走,内核提供的这种机制称为进程间通信(IPC, InterProcess Communication)

在这里插入图片描述

         在Linux下由很多种进程间通信的方式,在下面主要介绍八种,分别是:匿名管道(PIPE)、命名管道(FIFO)、信号、共享内存映射、共享内存、消息队列、信号量、UNIX域套接字。现在常用的进程间通信方式有:

  1. 管道(使用简单)
  2. 信号(开销最小)
  3. 共享内存(效率最高)
  4. UNIX域套接字(最稳定)

        

1、匿名管道(PIPE)

        管道是一种最基本的IPC,用于有血缘关系的进程之间。使用的系统调用如下:

int pipe(int pipefd[2]);

管道有如下特征:

  • 本质是一个伪文件(实为内核缓冲区)
  • 由两个文件描述符引用,一个表示写端,一个表示读端
  • 数据从管道的写端流入,从读端流出
  • 采用半双工通信方式,数据只能从一个方向上流动。如果需要全双工,可以创建两个管道。

**管道的原理:**管道实为内核使用环形队列机制,借助内核缓冲区实现(大小4K)

父子进程通过管道通信:

#include 
#include 
#include 
#include 
#include 
#include 

int main() {
    int fds[2];
    pid_t pid;
    int ret;
    char buf[1024];

    if (pipe(fds) < 0) {
        perror("open pipe error:");
        exit(1);
    }
    pid = fork();
    if (pid < 0) {
        perror("fork error:");
        exit(2);
    } else if (pid > 0) {  // parend process
        // parent process write to pipe
        close(fds[0]);   // close read end
        ret = write(fds[1], "I am your father", strlen("I am your father"));
        if (ret < 0) {
            perror("parent write pipe error:");
            exit(3);
        }
        printf("parent process id:%u, son:%u, write success\n", getpid(), pid);

        wait(NULL);
    } else {  // child process
        // child process read from pipe
        close(fds[1]);           // close wirte end
        ret = read(fds[0], buf, sizeof(buf));
        if (ret < 0) {
            perror("child read pipe error:");
            exit(3);
        }
        buf[ret] = '\0';
        printf("child process id:%u, father:%u, read from pipe:%s\n", getpid(), getppid(), buf);
    }
    
    return 0;
}

运行结果:

parent process id:2663, son:2664, write success
child process id:2664, father:2663, read from pipe:I am your father

管道读写的注意事项:

读管道:

  • 管道中有数据,读出数据,返回读到的字节数
  • 管道中无数据:①如果写端被关闭,read返回0 ②写端没有关闭,read阻塞

写管道:

  • 管道读端全部关闭:进程异常终止(收到SIGPIPE信号,最好捕捉SIGPIPE信号,是进程不终止)
  • 管道读端没有全部关闭:①管道已满,write阻塞 ②管道未满,将数据写入,返回写入的字节数

匿名管道的优劣:

优点:使用简单

缺点:①只能单向通信 ②只能用于父子、兄弟进程间通信

        

2、命名管道(FIFO)

        FIFO常被称为命名管道,以区分管道(PIPE)。PIPE只能用于有"血缘关系"的进程间通信,但FIFO可以在不相关的进程间实现通信。

        FIFO是Linux基础文件中的一种,但FIFO在磁盘上没有数据块,仅仅用来标识内核中的一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。

函数原型:

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

        一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。

FIFO进程间通信案例:

/* reader.c  */
#include 
#include 
#include 
#include 
#include 
#include 

int main() {
    int fd;
    char buf[1024];
    int n;
    
    if (mkfifo("/tmp/myfifo", 0644) < 0) {
        perror("mkfifo error:");
        exit(1);
    }

    if ((fd = open("/tmp/myfifo", O_RDONLY)) < 0) {
        perror("open fifo error:");
        exit(1);
    }
    if ( ( n = read(fd, buf, sizeof(buf))) < 0) {
        perror("read msg error:");
        exit(1);
    }
    buf[n] = '\0';

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

    unlink("/tmp/myfifo");
    return 0;
}

/* writer.c */
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main() {
    int fd;
    const char *msg = "hello from writer"; 

    if ((fd = open("/tmp/myfifo", O_WRONLY)) < 0) {
        perror("open fifo error:");
        exit(1);
    }
    if (write(fd, msg, strlen(msg)) < 0) {
        perror("write msg error:");
        exit(1);
    }

    return 0;
}

        

3、信号

        信号是由用户、系统或进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。

在shell终端输入kill -l 来查看支持的信号:

[root@master ~]# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

Linux信号可由下面的条件产生:

  • 对于前台进程,用户可以输入特殊终端字符来发送,比如输入Ctrl+C通常会给正在运行的进程发送一个中断信号
  • 系统异常,比如浮点异常和非法内存段访问
  • 系统状态变化,比如alarm定时器到期时将引起SIGALRM信号
  • 在终端运行kill命令或在程序中调用kill函数,例如:如果要杀死一个进程,我们可以使用 kill -9 pid 来杀死进程,-9表示发送9号信号,也就是SIGKILL信号,pid为发送信号的目标进程的进程ID

发送信号的系统调用:

int kill(pid_t pid, int sig);   // 给进程ID为pid的进程发送sig信号

信号的机制:

        进程或用户A给一个进程B发送信号,B在收到信号之前执行自己的代码,当B进程收到信号后,不管程序执行到什么位置,都要暂停运行,去处理信号,也就是调用信号处理函数,处理完再继续执行。与硬件中断类似——异步模式。但信号是软件层面实现的中断,早期常被成为“软中断”。

信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。

每个进程收到的所有信号,都是由内核负责发送的,内核处理。
        

4、共享内存映射

        存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。

         使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。

Linux进程间通信(Linux IPC)_第1张图片

相关系统调用:

/*
	建立映射
	addr: 建立映射区的地址,一般传NULL来由内核指定
	length:创建的映射区的大小
	prot:映射区权限
	flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
	fd:用来建立映射区的文件描述符
	offset:映射文件的偏移(4K的整数倍)
*/
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
/*
	同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放。
*/
int munmap(void *addr, size_t length);

mmap父子进程通信

        父子等有血缘关系的进程之间也可以通过mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags:

  • MAP_PRIVATE: (私有映射) 父子进程各自独占映射区;
  • MAP_SHARED: (共享映射) 父子进程共享映射区;

mmap无血缘关系进程间通信

        实质上mmap是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可。若想实现共享,当然应该使用MAP_SHARED了。

        

5、共享内存

        共享内存允许多个进程访问同一内存空间,是在多个进程间共享数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存链接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它进程读到的数据也将会改变。同样进程也可以和这块共享内存解除关联,接触关联后就不能操作这块共享内存了。

        共享内存并未提供锁机制,也就是说,在一个进程对共享内存进行读写时,不会阻止其它进程的读写。共享内存需要借助其它机制来保证进程间的数据同步(比如信号量)。

        使用ipcs命令可以查看共享内存、消息队列和信号量的相关信息:

[root@master uds]# ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     

共享内存相关函数:

#include 
#include 


/*
	创建或获取共享内存
	key: 共享内存的键值,是共享内存在系统中的编号,不同共享内存编号不能相同,用十六进制比较好
	size: 共享内存的大小,单位byte
	shmflg: 共享内存的访问权限,与文件差不多, 0666 | IPC_CREAT 表示所有用户都可读写,IPC_CREAT 表示如果不存在则创建
	return val: 成功返回一个id,失败返回-1,并设置errno
*/
int shmget(key_t key, size_t size, int shmflg);

/*
	将共享内存链接到当前进程的地址空间
	shmid: shmflg返回的id
	shmaddr: 共享内存链接到当前进程中的地址位置,通常为NULL,表示让系统来选择
	shmflg: 标志位,通常为0
	return val: 成功返回共享内存段的地址,失败返回 (void *)-1, 并设置errno
*/
void *shmat(int shmid, const void *shmaddr, int shmflg);

/*
	将共享内存从当前进程分离
	shmaddr: 共享内存地址
	return val: 成功返回0, 失败返回-1, 设置errno
*/
int shmdt(const void *shmaddr);

/*
	操作共享内存,可以用来删除一个共享内存
	shmid: 共享内存id
	cmd: 执行的操作, IPC_RMID表示删除
	buf: 设置为NULL
	return val: 失败返回-1, 设置errno
*/
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

使用案例:一个进程从文件读出内容写入共享内存,另一个进程读共享内存数据,保存入一个新文件

/* sharemem_r.c */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXBUFSIZE 4096
#define SHAREMEMESIZE 4096

int write_file(const char *buf, int size, const char *filename);

int main() {
    int shm_id;
    char buf[MAXBUFSIZE] = {0};
    char *shm_p = NULL;
    
    /* 
        获取共享内存,如果不存在则创建
    */
    if ( (shm_id = shmget(0x666, SHAREMEMESIZE, 0644)) < 0) {
        perror("shmget error");
        return 1;
    }

    /*
        把共享内存链接到当前进程的地址空间
     */
    shm_p = (char *)shmat(shm_id, NULL, 0);
    if (shm_p == (void *) -1) {
        perror("shmat error:");
        return 1;
    }

    // 拷贝共享内存中的数据
    memcpy(buf, shm_p, MAXBUFSIZE);

    // 将共享内存中的内容写入write_text文件中
    write_file(buf, strlen(buf), "write_text");

    printf("write to file: %s\n", buf);

    // 把共享内存从当前进程分离
    shmdt(shm_p);

    //删除共享内存
    if (shmctl(shm_id, IPC_RMID, 0) == -1) { 
        printf("shmctl failed\n"); 
        return -1; 
    }

    return 0;
}

// 将buf中的数据写入文件
int write_file(const char *buf, int size, const char *filename) {
    int fd, n;

    // 打开文件,不存在则创建
    if ((fd = open(filename, O_WRONLY | O_CREAT, 0644)) < 0) {
        perror("open file error:");
        return -1;
    } 

    // 写入内容
    n = write(fd, buf, size);
    if (n < 0) {
        perror("write file error");
        return -1;
    }

    close(fd);
    return n;
}

/* sharemem_w.c */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXBUFSIZE 4096
#define SHAREMEMESIZE 4096

int read_file(char *buf, int size, const char *filename);

int main() {
    int n, shm_id;
    char buf[MAXBUFSIZE] = {0};
    char *shm_p = NULL;

    /* 
        获取共享内存,如果不存在则创建
    */
    if ( (shm_id = shmget(0x666, SHAREMEMESIZE, 0644 | IPC_CREAT)) < 0) {
        perror("shmget error");
        return 1;
    }

    /*
        把共享内存链接到当前进程的地址空间
     */
    shm_p = (char *)shmat(shm_id, NULL, 0);
    if (shm_p == (void *) -1) {
        perror("shmat error:");
        return 1;
    }

    // 从文件中读取数据
    n = read_file(buf, MAXBUFSIZE, "./read_text");
    if (n == -1) {
        return 1;
    }

    printf("read from file: %s\n", buf);

    // 将数据拷贝到共享内存
    memcpy(shm_p, buf, strlen(buf));

    // 把共享内存从当前进程分离
    shmdt(shm_p);

    return 0;
}

// 读文件内容到buf
int read_file(char *buf, int size, const char *filename) {
    int fd, n;

     // 打开read_test
    if ( (fd = open(filename, O_RDONLY)) < 0) {
        perror("open file_read_test error:");
        return -1;
    }
    // 读取文件内容
    n = read(fd, buf, size);
    if (n < 0) {
        perror("read file error:");
        return -1;
    }

    // 关闭文件
    close(fd);

    return n;
}

        

6、消息队列

        消息队列本质是存在与内核中的消息链表,一个消息队列由一个标识符来标识,进程可以从中读写数据。消息队列不属于某个进程,创建之后,即使该进程结束,消息队列依然存在,其他进程依然可以读写数据。消息队列与后面介绍的UNIX域套接字相比,在速度上没有多少优势。

相关系统调用:

/*
	获取或创建一个消息队列
*/
int msgget(key_t key, int msgflg);

/*
	向消息队列中发送消息
	msgp: 指向类似下面结构的结构体
		struct msgbuf {
        	long mtype;       
       		char mtext[1];    
        };
	msgsz: 上面结构体的mtext的大小由该字段指定,发送的时候可以使用strlen(buf.mtext), 接收的时候sizeof(buf.mtext)
	msgflg: 通常为0
*/
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

/*
	从消息队列中取出一条消息
*/
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
               int msgflg);

生产者消费者示例:

/* producer.c */
#include 
#include 
#include 
#include 
#include 

typedef struct {
    long mtype;
    char mtext[1024];
}msgbuf;


int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;

    if ((key = ftok(".", 666)) == -1) {
        perror("ftok error:");
        return 1;
    }

    if ((msgid = msgget(key, 0644 | IPC_CREAT)) == -1) {
        perror("msgget error:");
        return 1;
    }

    msgbuf buf = {1, "surprise, mother fucker"}; 

    if (msgsnd(msgid, &buf, strlen(buf.mtext), 0) == -1) {
        perror("msgsnd error");
        return 1;
    }

    return 0;
}

/* consumer.c */
#include 
#include 
#include 
#include 
#include 

typedef struct {
    long mtype;
    char mtext[1024];
}msgbuf;


int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;

    if ((key = ftok(".", 666)) == -1) {
        perror("ftok error:");
        return 1;
    }

    if ((msgid = msgget(key, 0644 | IPC_CREAT)) == -1) {
        perror("msgget error:");
        return 1;
    }

    msgbuf buf;

    if (msgrcv(msgid, &buf, sizeof(buf.mtext), 1, 0) == -1) {
        perror("msgrcv error");
        return 1;
    }

    printf("recv msg, type:%ld, text:%s\n", buf.mtype, buf.mtext);

    msgctl(msgid, IPC_RMID, NULL);

    return 0;
}

        

7、信号量

        信号量是一个计数器,它主要用于在多个进程间的数据同步。

信号量的PV操作:

  • P操作:我们将申请信号量称为P操作,申请信号量的本质就是申请获得临界资源中某块资源的使用权限,当申请成功时临界资源中资源的数目应该减一,因此P操作的本质就是让计数器减一。
  • V操作:我们将释放信号量称为V操作,释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一,因此V操作的本质就是让计数器加一。

信号量有两种:

System V信号量:常用于进程间同步

相关系统调用:

int semget(key_t key, int nsems, int semflg);

int semop(int semid, struct sembuf *sops, unsigned nsops);
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);

int semctl(int semid, int semnum, int cmd, ...);

Posix信号量,常用于线程间同步

相关函数:

int sem_init(sem_t *sem, int pshared, unsigned int value);

int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

int sem_getvalue(sem_t *sem, int *sval);

int sem_post(sem_t *sem);
int sem_destroy(sem_t *sem);

        

8、UNIX域套接字(Unix Domain Socket)

        socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是Unix Domanin Socket(UDS)。虽然网络socket也可以用于同一台主机的进程间通讯,但是UDS用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答号等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为IPC是可靠的通讯,而网络协议是为不可靠通讯设计的。UDS也提供面向连接和面向数据报两种API接口,类似于TCP与UDP,但是面向数据报的UDS也是可靠的,消息既不会丢失也不会顺序错乱。

        UDS是全双工的,API接口语义丰富,相比于其它IPC有明显的优越性,目前已经成为使用最广泛的IPC机制。例如Docker cli与Docker 守护进程的通信就会使用UDS。

        使用UDS的步骤与网络socket差不多,也要使用到socket()、bind()、listen()、accept等系统调用。

        UDS与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。

UDS的C/S架构案例:

/* server.c */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


int server_listen(const char *name) {
    int fd, len, err, ret;
    struct sockaddr_un un;

    // 创建unix domain socket
    if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
        return -1;
    }

    // 以防存在,先删除
    unlink(name);

    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

    // 绑定uds
    ret = bind(fd, (struct sockaddr *)&un, len);
    if( ret == -1) {
        close(fd);
        perror("bind error");
        return -1;
    }

    if(listen(fd, 128) < 0) {
        close(fd);
        perror("listen error");
        return -1;
    }

    return fd;
}

int server_accept(int listenfd, uid_t *uidptr)
{
	int clifd, len, err, 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; 
    }
		
	len -= offsetof(struct sockaddr_un, sun_path); 
	un.sun_path[len] = 0; 

	if (stat(un.sun_path, &statbuf) < 0) {
		close(clifd);
        return -1;
	}
	if (S_ISSOCK(statbuf.st_mode) == 0) {
		close(clifd);
        return -1;
	}
	if (uidptr != NULL)
		*uidptr = statbuf.st_uid; 
	
	unlink(un.sun_path); 

	return(clifd);
}



int main(int argc, char const *argv[])
{
    int lfd, cfd, n, i;
	uid_t cuid;
	char buf[1024];
    while (1) {
       lfd = server_listen("tmp.sock");
        if (lfd < 0) {
            exit(-1);
        }

        cfd = server_accept(lfd, &cuid);
        if (cfd < 0) {
            exit(-1);
        }

        while (1) {

            n = read(cfd, buf, 1024);
            if (n == -1) {
                break;
            } else if (n == 0) {
                printf("the other side has been closed.\n");
                break;
            }

            for (i = 0; i < n; i++) {
                buf[i] = toupper(buf[i]);
            }
            write(cfd, buf, n);
        } 
    }
    
	close(cfd);
	close(lfd);
	return 0;

}


/* client.c */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define CLI_PATH "/var/tmp/" 

int cli_conn(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;

	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        return -1;
    }
		
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
	len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

	unlink(un.sun_path); 
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
        perror("bind error");
		close(fd);
        return -1;
	}

	/* fill socket address structure with server's address */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	if (connect(fd, (struct sockaddr *)&un, len) < 0) {
		perror("connect error");
        close(fd);
        return -1;
	}

    return fd;
}
int main(void)
{
	int fd, n;
	char buf[1024];

	fd = cli_conn("tmp.sock");
	if (fd < 0) {
		exit(-1);
	}

	while (fgets(buf, sizeof(buf), stdin) != NULL) {
		write(fd, buf, strlen(buf));
		n = read(fd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, n);
	}
	close(fd);

	return 0;
}

socketpair

        使用socketpair可以创建一对无名的相连的socket,可以用于有血缘关系的进程间的通信,使用起来比较类似于管道,但它是全双工的。

相关系统调用:

/*
	The socketpair() call creates an unnamed pair of connected sockets in the specified domain
	domain: 在linux上,只支持AF_UNIX
	sv: 一对用于通信的fd
*/
int socketpair(int domain, int type, int protocol, int sv[2]);

你可能感兴趣的:(linux,linux,服务器,c++)