Linux进程间通信(Linux IPC)

Linux 进程间通信 Linux进程通信继承了传统的UNIX进程间通信方式,包括无名管道、有名管道以及信号;还有System V进程间通信(IPC),包括System V消息队列、System V信号量以及System V共享内存;套接字。 阅读详情

前言

        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 <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

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函数来实现。

在这里插入图片描述

相关系统调用:

/*
	建立映射
	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 <sys/ipc.h>
#include <sys/shm.h>


/*
	创建或获取共享内存
	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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#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 <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

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 <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>


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];
	
	lfd = server_listen("tmp.sock");
    if (lfd < 0) {
         exit(-1);
     }
    while (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 <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#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的过程中对linux进程通信进行记录学习。现在在 Linux 中使用较多的进程间通信方式主要有以下几种。 (1)管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信, 有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。 (2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信 方式,用于通知接受进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求 效果上可以说是一样的。 (3)消息队列:消息 阅读详情

相关推荐

Linux——网络通信

包括多种机制,如管道、消息队列、信号、共享内存和信号量。这些机制都依赖于Linux内核提供的支持,用于实现不同进程之间的数据交换和同步。然而,这些通信方式在本地进程间通信中非常有用,但无法直接用于跨机器间的通信。

m0_73510889的博客 3468

Linux —— 进程间通信

进程间通信方式的介绍:管道通信、system V通信......

M的博客 6665

Linux进程间通信IPC的几种方式简介

Linux进程通信的源头       linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T(原为American Telephone & Telegraph的缩写,也是中文译名美国电话电报公司由来)的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手段

Sunshine的专栏 7578

LINUX---进程间通信(IPC)

通信

weixin_48651223的博客 3493

linux——进程间通信IPC

Linux中七种IPC详解,进程间通信

a_zxswer的博客 793

遥感图像融合实战:5分钟搞懂全色与多光谱图像如何提升分辨率(附Python代码)

本文通过实战案例,详细解析了如何利用全色锐化技术融合全色与多光谱遥感图像,以提升图像分辨率。文章深入浅出地介绍了核心概念、环境配置、经典GS融合算法的Python代码实现,并对比了不同算法的优劣,最后提供了效果评估方法与实战陷阱分析,帮助读者快速掌握遥感图像融合的关键技术。

weixin_29041195的博客 394

Linux 进程间通信(IPC)详解

在操作系统里,不同进程间常常需要进行数据交换、同步协调等操作,进程间通信(Inter - Process Communication,IPC)机制应运而生。在Linux系统中,常见的IPC机制有管道、信号量、共享内存、消息队列和套接字。这些机制各自具备独特的特性,适用于不同的应用场景,并且在各类系统和应用程序开发中得到广泛应用,也是技术面试中的重点考查内容。

w20010915的博客 2248

ADS新手必看:5分钟搞定Smith圆图阻抗匹配(附详细操作截图)

本文为射频电路设计新手提供了在ADS软件中快速使用Smith圆图进行阻抗匹配的实战指南。通过清晰的步骤和操作截图,详细演示了如何从负载阻抗点出发,通过添加电感和电容元件,在5分钟内完成向50欧姆标准阻抗的匹配,并介绍了仿真验证与性能调优的方法,帮助工程师和学生直观掌握这一核心技能。

weixin_29261711的博客 392

Linux进程间通信(IPC)深入解析

简单数据流传输:优先考虑管道(有亲缘关系)或FIFO(无亲缘关系)。高性能大规模数据共享共享内存是不二之选,但必须精心设计同步机制(信号量、文件锁等)。结构化消息传递消息队列或D-Bus(在桌面环境中)更为合适。进程同步与控制信号量用于同步,信号用于异步事件通知。网络通信或本地跨语言通信套接字提供了最广泛的支持和标准化接口。

X 1508

【工具使用】Keil常用的调试操作整理介绍

2、当选择了非0级优化时,调试可能会变得困难,具体表现在断点调试。该按钮是在进入了函数单步调试的时候,有时候你可能不必再执行该函数的剩余部分了,通过该按钮就直接一步执行完函数余下的部分,并跳出函数,回到函数被调用的置。:如果是在C语言窗口中,则是按单条语句执行,与单步调试不同的是,遇到函数不会进入函数内部,而是直接全速运行函数,并跳到下一条语句。但我的Keil软件中无法显示这个视图,并且通过修改系统试图,调试时,点击打开系统视图发生了闪退的现象,目前能想到的原因是:重新安装Keil,安装最新版本。

MEYOU_Cc 不吃香菜! 1万+

论文超详细精读|万字:2s-AGCN

1. 提出了一种新的双流自适应图卷积网络(2S-AGCN)用于基于骨架的动作识别。**模型中的图的拓扑既可以统一地学习,也可以通过BP算法以端到端的方式单独学习。** 2. 提出了一种同时对**一阶和二阶信息(骨架的长度与方向)建模的双流框架**,在识别精度上有了显著的提高。 它将骨架数据的图结构参数化,并嵌入到网络中,与模型共同学习和更新。 3. 文章主要创新点有两个,一个是双流网络,另外一个是自适应。

IanYue的博客 6383

Linux操作系统:进程间通信IPC方式

在 Linux 系统中,进程间通信至关重要。不同的进程通常拥有独立的虚拟地址空间,这保证了进程的独立性,但也使得进程间无法直接通信。为了解决这个问题,Linux 内核提供了多种进程间通信方式。数据传输:一个进程可能需要将数据发送给另一个进程。例如,在分布式计算环境中,不同的计算节点可能需要相互传递数据以完成复杂的任务。共享数据:多个进程可能希望操作共享的数据。当一个进程修改了共享数据时,其他共享该数据的进程应能立即看到这个变化。这在多线程编程或并行计算中非常常见,能够提高系统的效率和协同工作能力。

深度Linux 1422

YOLO26改进策略【Neck】| CVPR2025 LCA轻量交叉注意力 跨层级双向引导 + 分层降噪增强,提升微小目标特征表达

简单相加、拼接融合无法建立深浅层双向约束关系,浅层微小目标易被深层背景覆盖,深层全局信息难以修正浅层噪声;普通单路自注意力仅单一层内建模依赖,缺少跨层级特征互引导机制,深浅特征信息割裂;标准多头跨注意力计算量大、参数量高,轻量化单模态检测/分割网络部署困难,无法区分前景目标与背景噪声。为此设计单模态轻量化交叉注意力LCA,构建浅层细节、深层语义双分支双向引导结构,轻量完成跨层级特征交互,同步降噪并强化单模态微小目标特征。

Limiiiing的博客 523

Linux第十讲:进程间通信IPC

Linux第十讲:进程间通信IPC

2301_79761834的博客 1165

HBase批量导入工具:BulkLoad使用详解

HBase作为分布式NoSQL数据库,在大数据领域有着广泛应用。传统的数据写入方式(如Put操作)在面对海量数据导入时存在性能瓶颈。BulkLoad提供了一种高效的大批量数据导入方案,可以显著提高数据加载速度并降低集群负载。BulkLoad的核心原理和架构与传统写入方式的性能对比详细的使用方法和实现步骤性能优化技巧和最佳实践实际应用案例分析本文首先介绍BulkLoad的基本概念和原理,然后深入讲解其实现机制和关键技术。接着通过实际案例演示BulkLoad的使用方法,并分析性能优化策略。

大数据洞察的博客 1236

基于Simulink的电动汽车动力总成控制策略验证与标定仿真平台

电动汽车的动力总成(Powertrain)是车辆的核心系统,包括动力电池、电机控制器、逆变器和驱动电机等关键部件。动力总成的控制策略直接影响车辆的动力性能、能耗效率和驾驶体验。为了验证和优化这些控制策略,基于MATLAB/Simulink的仿真平台成为一种高效且经济的解决方案。本项目旨在构建一个完整的基于Simulink的电动汽车动力总成控制策略验证与标定仿真平台,涵盖控制策略建模、性能评估以及参数标定等功能模块,并通过仿真验证系统的有效性。

xiaoheshang_123的博客 177

【Linux进程间通信】一、什么是IPC

本篇为Linux进程间通信第一篇文章,介绍什么是进程间通信IPC,关于进程间通信的具体方式及实现在后面会一一更新,更多内容请关注我的专栏《Linux从小白到大神》。

仅以此博客记录日常学习工作中所思所得,水平有限,难登大雅,万望海涵。 847

可编程逻辑控制器(PLC)系列:Mitsubishi MELSEC-Q_(8).MELSEC-Q系列PLC故障诊断与维护

MELSEC-Q系列PLC的故障诊断与维护是一个系统性的过程,需要工程师具备扎实的理论基础和丰富的实践经验。通过了解故障类型、使用诊断工具、遵循诊断步骤,可以有效地定位和解决故障问题。同时,定期的检查与保养、备份与恢复、优化程序和配置,以及提供良好的运行环境,可以减少故障的发生,提高系统的可靠性。希望本文能够帮助工程师在故障诊断与维护过程中更加得心应手,确保MELSEC-Q系列PLC的稳定运行。

2401_87715305的博客 1247

Linux进程控制与进程间通信(IPC)全面指南

Linux进程控制与进程间通信(IPC)全面指南

weixin_66055196的博客 1375

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Linux进程间通信(IPC)

pipe 函数接受一个参数,是包含两个整数的数组,如果调用成功,会通过 pipefd[] 传出给用户程序两个文件描述符,需要注意 pipefd[0] 指向管道的读端, pipefd[1] 指向管道的写端,那么此时这个管道对于用户程序就是一个文件,可以通过 read(pipefd [0]);如果有指向管道写端的文件描述符没关闭,而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

m0_46417380的博客 1010

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Linux 进程间通信-IPC 机制

Linux 平台上在 Kernel 协调下完成进程之间的相互通信,有多种进程间通信 —— Inter Process Communication(IPC)方式。 1. IPC 分类 按照功能用途来看有三种基本的进程间通信类型,分别用户信息交换(Communication),同步(Synchronization)和信号(Signal)。另外,在基本的 IPC 通信机制之上还存在更为复杂和广泛应用的进程间通信机制,通常提供了丰富和更高层次的封装以便于应用开发,比如 Android 平台采用的 Binder

Hugo的博客 2184

【Linux】进程间通信(IPC)

通过结合具体需求(数据量、实时性、开发成本)选择合适的IPC机制。

weixin_43510208的博客 1073

Linux进程间通信的秘密通道:IPC机制详解

在 Linux 操作系统中,进程是操作系统的基本运行单位。由于进程间具有独立的地址空间,如何实现进程之间的数据交换和同步成为系统设计中的一个核心问题。为了解决这一问题,Linux 提供了多种进程间通信(Inter-Process Communication, IPC)机制。这些机制在性能、适用场景和复杂性方面各具特点,开发者可以根据具体需求选择合适的通信方式。 本文将从概念出发,详细讲解 Linux 的 IPC 机制,包括管道、消息队列、共享内存、信号量和信号等,帮助读者深入理解 IPC 的原理和应用场景

fudaihb的博客 1032

linux进程间通信IPC方式

Linux环境下,进程地址空间相互独立、彼此隔离,因此进程间的数据不能直接访问。如果要交换数据,必须要通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷贝到内核缓冲区,进程B再把数据从内核缓冲区拷贝走,内核提供的这种机制称为进程间通信(IPC, InterProcess Communication)。在Linux下由很多种进程间通信的方式,分别是:匿名管道(PIPE)、命名管道(FIFO)、信号、共享内存、消息队列、信号量、UNIX域套接字。现在常用的进程间通信方式有:管道(使用简单)

lx123010的专栏 1218

Linux——进程间通信IPC

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。 ...

一个小孩 268
上一篇: Golang+Mysql+Vue+Nginx前后端分离项目--个人博客
下一篇: Golang源码探究 —— map
CoreDump丶
博客等级 码龄8年 451粉丝 70原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值