2022-2-21 操作系统八股之七 —— 进程间的通信方式有哪些?

本文详细介绍了进程间通信的几种方式,包括匿名管道、有名管道(FIFO)、消息队列、信号量和共享内存。匿名管道用于父子进程间通信,有名管道可在无关进程间通信,消息队列提供有序、可存储的消息传递,信号量用于同步和互斥,共享内存则允许多个进程直接访问同一块内存区域。每种通信方式都有其特点和适用场景,如信号量常与共享内存结合用于资源管理。通过对这些机制的理解,开发者可以更好地实现进程间的高效通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程 ( InterProcess Communication )间通信是指在不同进程间传播或者交换信息。进程间通信的方式有:管道(有名管道和匿名管道)、消息队列、信号量、共享内存、Socket、Stream(这啥玩意儿我™完全不懂)、内存映射。

一 、匿名管道
1、半双工,有固定的读端和写端。
2、只能用于父进程和子进程之间。
3、可以看成是一种特殊的文件,可以使用普通的 read 、write 的函数对其进行读写操作。但不是普通文件,不存在于普通文件的系统当中,只是存在于内存当中。

管道有关的函数实现案例
1、获取管道文件的大小

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
    int pipefd[2];
    int ret = pipe(pipefd);

    if(ret == -1){
        perror("pipe");
        exit(0);
    }
    //fpathconf()获取文件相关的配置信息
    long size = fpathconf(pipefd[0],_PC_PIPE_BUF);

    printf("pipe size : %ld \n",size);

}
/*pipe()  creates  a pipe, a unidirectional(单向) data channel that can be used
       for interprocess communication.  The array pipefd is used to return two
       file  descriptors  referring to the ends of the pipe.  pipefd[0] refers
       to the read end of the pipe.  pipefd[1] refers to the write end of  the
       pipe.   Data  written  to  the write end of the pipe is buffered by the
       kernel until it is read from the read end of the pipe.  */

fpathconf ()

/*
fpathconf() gets a value for the configuration option name for the open
       file descriptor fd.
 _PC_PIPE_BUF
              The maximum number of bytes that can be written atomically to  a
              pipe  of  FIFO.   For  fpathconf(), fd should refer to a pipe or
              FIFO.  For fpathconf(), path should refer to a FIFO or a  direc‐
              tory;  in the latter case, the returned value corresponds to FI‐
              FOs created in  that  directory.   The  corresponding  macro  is
              _POSIX_PIPE_BUF.
              */

2、父子进程间匿名管道通信

/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用于进程间的通信
参数:传出参数。(那不得malloc)
    pipefd[0] 读端的文件描述符
    pipefd[1] 写端的文件描述符
返回值:
-成功:0
-失败:-1,并且设置 perror
注意:匿名管道只能用于具有关系的进程间的通信,比如说(父子进程,兄弟进程,孙子进程)

*/
//子进程发送数据给父进程,父进程读取到数据之后读出
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(void){
    //管道创建,需要在 fork 之前还是在 fork 之后?
    //需要在 fork 之前创建,感觉在 fork 之后创建会导致创建了两个管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1){
        perror("pipe");
        exit(0);
    }
    pid_t pid = fork();
    if(pid > 0){
        printf("i am parent process, pid : %d .\n",getpid());
        //父进程如果不写数据可以将写端关闭了
        close(pipefd[1]);
        while (1)
        {
            // char *str = "hello, i am parent.";
            // int len2= write(pipefd[1],str,strlen(str));
            // sleep(1);

            char buf[1024] = {0};
            int len1 = read(pipefd[0],buf,sizeof(buf));
            printf("parent recv : %s ,pid : %d.\n",buf,getpid());
        }
    }
    else if(pid == 0){
        printf("i am child process, pid : %d .\n",getpid());
      //  sleep(10);//通过延后写入数据来达到管道阻塞的效果
      //子进程如果不读数据可以将读端关闭了
        close(pipefd[0]);
      while (1)
      {
        char *str = "hello, i am child.";
        int len1 = write(pipefd[1],str,strlen(str));
        // sleep(1);

        // char buf[1024] = {0};
        // int len2 = read(pipefd[0],buf,sizeof(buf));
        // printf("child recv : %s ,pid : %d.\n",buf,getpid());
        // bzero(buf,1024);//可以将缓冲区清空
        

      }    
//sizeof运算符计算出来的是字节,计算最后的'\0'
//strlen 函数计算出来的是字符串的长度,不包括最后面的'\0'
    }
}

父子进程进行管道的读写必须一个进程关闭读管道,一个进程关闭写管道,否则会造成一个进程从管道中读取了自己写入的数据。

二、有名管道 FIFO
1、FIFO 可以在无关的进程之间交换数据,与无名管道不同。
2、FIFO 有相关联的路径名称。以特殊设备文件形式存在于文件系统之中。

有名管道写端

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(void){

    //1、检查管道是否存在,如果不存在就创建一个管道
    int ret = access("test",F_OK);
    if(ret == -1){
        printf("管道文件不存在,开始创造新的管道文件.\n");
    //2、创建管道
        ret = mkfifo("test",0664);

        if(ret == -1){
            perror("test");
            exit(0);
        }
        printf("管道创建成功.\n");
    }

    //3、以 WDONLY的方式打开创建的管道
    //管道的读端和写端的文件描述符最后同时在两个不同的进程上面,防止同一个进程读到自己写入的数据
    int fd = open("test",O_WRONLY);
    if(fd == -1){
        perror("open");
        exit(0);
    }
    //向管道中写入数据
    for(int i = 0;i < 100;++i){
        char buf[1024];
        sprintf(buf,"hello, %d.",i);
        printf("write data: %s.",buf);
        write(fd,buf,strlen(buf));
        sleep(1);
    }
    return 0;
}

有名管道读端

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(void){
    int fd = open("test",O_RDONLY);
    if(fd == -1){
        perror("open");
        exit(0);
    }
    while (1)
    {
        char buf[1024];
        int len = read(fd,buf,sizeof(buf));
        if(len == 0){
            printf("管道还未创建,正在创建中。");
            break;
        }
        printf("recv buf : %s.",buf);
    }
    close(fd);
    return 0; 
}

三、消息队列
1、是存放消息的链表,每个消息都有单独的标识符 ID 来标识。
2、面向记录,其中的消息具有特定的格式和特定的优先级。
3、独立于发送和接受进程,当进程终止的时候,消息队列及内容不会被删除。
4、实现消息的随机查询,消息不一定要按照先进先出的顺序读取,也可以按照消息的排放顺序读取。
相关的函数调用
1)ftok()
消息队列通信需要 一个中间的介质。为了区分这种介质,需要一个为一个标号进行标识,ftok()就是用来产生这个标识的函数。
关于 ftok()的详细介绍文章:ftok()函数深度解析

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

       key_t ftok(const char *pathname, int proj_id);
作用:为指定了文件路径名的文件加上一个标识。
DESCRIPTION
       The  ftok()  function  uses  the identity of the file named by the given pathname (which
       must refer to an existing, accessible file) and the least significant 8 bits of  proj_id
       (which must be nonzero) to generate a key_t type System V IPC key, suitable for use with
       msgget(2), semget(2), or shmget(2).

       The resulting value is the same for all pathnames that name the same file, when the same
       value  of  proj_id is used.  The value returned should be different when the (simultane‐
       ously existing) files or the project IDs differ.

2、msgget() 创建一个消息队列

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

       int msgget(key_t key, int msgflg);

DESCRIPTION
       The  msgget()  system call returns the System V message queue identifier associated with
       the value of the key argument.  It may be used either to obtain the identifier of a pre‐
       viously  created  message  queue  (when  msgflg  is zero and key does not have the value
       IPC_PRIVATE), or to create a new set.

3、

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

       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);

DESCRIPTION
       The  msgsnd()  and  msgrcv() system calls are used to send messages to, and receive mes‐
       sages from, a System V message queue.  The calling process must have write permission on
       the message queue in order to send a message, and read permission to receive a message.

       The  msgp  argument  is a pointer to a caller-defined structure of the following general
       form:     struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[1];    /* message data */
           };

       The mtext field is an array (or other structure) whose size is  specified  by  msgsz,  a
       nonnegative  integer  value.  Messages of zero length (i.e., no mtext field) are permit‐
       ted.  The mtype field must have a strictly positive integer value.  This  value  can  be
       used by the receiving process for message selection (see the description of msgrcv() be‐
       low).

   msgsnd()
       The msgsnd() system call appends a copy of the message pointed to by msgp to the message
       queue whose identifier is specified by msqid.

3.msgctl()改变消息队列的行为模式

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

       int msgctl(int msqid, int cmd, struct msqid_ds *buf);
       msgctl()  performs  the control operation specified by cmd on the System V message queue
       with identifier msqid.

       The msqid_ds data structure is defined in <sys/msg.h> as follows:

           struct msqid_ds {
               struct ipc_perm msg_perm;     /* Ownership and permissions */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };
      //可以通过这个数据结构对消息队列进行精细化管理
       Valid values for cmd are:

       IPC_STAT
              Copy  information  from  the kernel data structure associated with msqid into the
              msqid_ds structure pointed to by buf.  The caller must have  read  permission  on
              the message queue.

       IPC_SET
              Write  the  values of some members of the msqid_ds structure pointed to by buf to
              the kernel data structure associated with this message queue, updating  also  its
              msg_ctime   member.    The  following  members  of  the  structure  are  updated:     
            
        IPC_RMID
        //删除消息队列
              Immediately  remove  the  message  queue, awakening all waiting reader and writer
              processes (with an error return and errno set to  EIDRM).   The  calling  process
              must  have appropriate privileges or its effective user ID must be either that of
              the creator or owner of the message queue.  The third argument to msgctl() is ig‐
              nored in this case.      

msg_s.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<string.h>
#include<sys/msg.h>
#include <sys/types.h>
//设计消息结构,必须以 long int 类型开始,作为消息队列的标识
struct mymesg
{
    long int mtype;
    char mtext[512];
};

int main(void){
    int id = 0;
    struct mymesg chxmsg;
    key_t key = ftok("./",66);
    id = msgget(key,IPC_CREAT | 0666);
    if(id == -1){
        perror("msgget");
        return 0;
    }

    while(1){
        char msg[512];
        memset(msg,0,sizeof(msg));
        chxmsg.mtype = 1;
        printf("input msg:");
        fgets(msg,sizeof(msg),stdin);
        strcpy(chxmsg.mtext,msg);

        int ret = msgsnd(id,(void*)&chxmsg,sizeof(chxmsg),0);
        if(ret == -1){
            perror("msgsnd");
            exit(-1);
        }
        //用来终止通信
        if(strncmp(msg,"quit",4) == 4)
        break;
    }
    if(msgctl(id,IPC_RMID,NULL) == -1){
        perror("msgctl");
        exit(-1);
    }
    return 0;
}

msg_c.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<string.h>
#include<sys/msg.h>
#include <sys/types.h>
struct mymesg
{
    long int mtype;
    char mtext[512];
};

int main(void){
    int id = 0;
    struct mymesg chxmsg;
    key_t key = ftok("./",66);
    id = msgget(key,IPC_CREAT | 0666);
    if(id == -1){
        perror("msgget");
        return 0;
    }

    while(1){
       
        int ret = msgrcv(id,(void*)&chxmsg,sizeof(chxmsg),1,0);
        if(ret == -1){
            perror("msgrcv");
            exit(-1);
        }
        //用来终止通信
        printf("data:%s.\n",chxmsg.mtext);
        if(strncmp(chxmsg.mtext,"quit",4) == 4)
        break;
    }
    
    return 0;
}

在这里插入图片描述
在这里插入图片描述
参考文章:Linux消息队列编程(简单应用)

四、信号量(计数器、原子操作、结合共享内存使用、操作任意数)
1、是一个计数器。用于进程间的互斥和同步,本身并不作为进程间通信的数据存储。
2、进程间同步,在进程传递数据需要结合共享内存。
3、信号量的实现基于操作系统的 PV 操作,程序对信号的操作都是原子操作。
4、对信号的 PV 操作不仅限于对信号的值加 1 还是减 1 ,而是可以加减任意正整数。
5、支持信号量组。

/*
    信号量的类型 sem_t
    int sem_init(sem_t *sem, int pshared, unsigned int value);
        - 初始化信号量
        - 参数
            - sem : 信号变量的地址
            - pshared : 0 用在线程间,非0 用在进程间
            - value : 信号量中的值

    int sem_destroy(sem_t *sem);
        - 释放资源

    int sem_wait(sem_t *sem);
        - 对信号量加锁,调用一次对信号量的值减一,如果为0就阻塞。


    int sem_trywait(sem_t *sem);

    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    int sem_post(sem_t *sem);
        - 对信号量解锁,调用一次对信号量减一
    int sem_getvalue(sem_t *sem, int *sval);

    sem_t psem;
    sem_t csem;
    init(psem, 0, 8);
    init(csem, 0, 0);

    producer() {
        sem_wait(&psem);
        sem_post(&csem)
    }

    customer() {
        sem_wait(&csem);
        sem_post(&psem)
    }

*/
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
//创建一个互斥量
pthread_mutex_t mutex;
sem_t psem;
sem_t csem;

struct Node
{
    int num;
    struct Node*next;
};

//头节点
struct Node*head = NULL;

void *producer(void*arg){
    //不断的创建新的节点,添加到链表中
    while (1)
    {
    //psem 记录能够用于生产的空间的数目
        sem_wait(&psem);
        pthread_mutex_lock(&mutex);
        struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand()%1000;
        printf("add node, num : %d,tid : %ld.\n",newNode->num,pthread_self());
    
        pthread_mutex_unlock(&mutex);
        //csem 记录可以用来消费的数目
        sem_post(&csem);
        usleep(100);
    }
    
    return NULL;
}
void *customer(void*arg){
    while (1)
    {
        //保存头节点的指针
        sem_wait(&csem);
        pthread_mutex_lock(&mutex);
      
        struct Node*tmp = head;
        //判断是否有数据
       
        head = head->next;
        printf("del node,num : %d,tid : %ld.\n",tmp->num,pthread_self());
        free(tmp);
        pthread_mutex_unlock(&mutex);
        sem_post(&psem);
        }
    return NULL;
}
int main(void){
    pthread_t ptids[5],ctids[5];
    pthread_mutex_init(&mutex,NULL);
    
    sem_init(&psem,0,8); //表示容器空位
    sem_init(&csem,0,0);//表示生产的物品数

    for(int i = 0;i < 5;i++){
        pthread_create(&ptids[i],NULL,producer,NULL);
        pthread_create(&ctids[i],NULL,customer,NULL);
    }
    for(int i = 0;i < 5;i++){
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }
    while (1)
    {
        sleep(100);
    }
    //不加 while 死循环会直接执行下面销毁互斥量的语句,那互斥量就没法用了
    pthread_mutex_destroy(&mutex);

    pthread_exit(NULL);

   // pthread_mutex_destroy(&mutex);
   //不能放这里,放这里直接退出了就无法执行了
    return 0;
}

五、共享内存
1、两个内存共享一个给定的存储区
2、共享内存是最快的一种 IPC ,因为程序是直接对内存进行读取。

共享内存使用的函数
int shmget(key_t key, size_t size, int shmflg);
-功能 :创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。新创建的内存段中的数据都会被初始化为 0-参数 : 
-key:key_t 类型是一个整型,通过这个去找到或者创建一个共享内存。一般用 16 进制表示,是一个非 0 值。(为什么是非零值呢?
-size :共享内存的大小,不能为0。最终的创建出来的共享内存的大小和系统分页的大小一致。
-shmflg:属性
-访问权限
-附加属性:创建/判断共享内存是否存在
-创建:IPC_CREATE
-判断共享内存是否存在:IPC_EXCL,需要和 IPC_CREATE 一起使用。
具体的使用方式 : IPC_CREATE | IPC_EXCL | 0664 
通过这些去使用的。
-返回值:
- -1 失败
- >0,成功,返回的共享内存引用的id,后面的操作共享内存都是通过这个值

void *shmat(int shmid, const void *shmaddr, int shmflg);
- 功能:和当前的进程进行关联
- 参数:
- shmid 共享内存的标识(ID),由 shmget 返回值获取。
- shmaddr 申请的共享内存的起始地址,指定 NULL,内核指定
- shmflg 对共享内存的操作
- 读:SHM_RDONLY 必须要有读的权限
- 读写 :0
- 返回值:
- 成功:返回共享内存的起始地址,
- 失败:(void*)(-1- 
 int shmdt(const void *shmaddr);
 -功能:解除当前进程和共享内存的关联
 -参数:shmaddr 共享内存的首地址
 
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
 -功能:删除共享内存,共享内存要删除才会消失,创建共享内存的进程被销毁了,对共享内存是没有影响的。
 -参数:
 shmid:共享内存的 id
 cmd:要做的操作
 IPC_STAT :获取共享内存当前的状态
 IPC_SET :设置共享内存的状态
 IPC_RMID:标记共享内存被销毁
 (是在标识还有其他的进程和该内存相关联的时候,当前的进程和这块内存的关联被解除了吗?)
 -buf:需要设置或者获取的共享内存的参数的信息,可以使用下面的参数:
 -IPC_STAT: buf 中存储数据
 -IPC_SET:buf 中需要初始化数据,设置到内核中
 -IPC_RMID:在删除共享内存的情况下,没有用 ,NULL
 
 key_t ftok(const char *pathname, int proj_id);
 -功能:根据指定的路径名和 int 值,生成一个共享内存的key
 -参数:
 -pathname 指定一个存在的路径
 /home/three/.vscode
 -proj_id int类型的值,但是系统调用自会使用其中的1个字节
 范围 0-255,一般指定一个字符 'a'
 
问题1:操作系统如何知道一块共享内存被多少个进程关联?
-共享内存维护了一个结构体 struct shmid_ds 这个结构体有一个成员 shm_nattch
-shm_nattch记录了共享内存的关联计数

问题2:可不可以对共享内存进行多次删除?(shmctl)
-可以的
-因为 shmctl 标记删除共享内存,不是直接删除
-什么时候真正删除呢?
当和共享内存关联的进程数为 0 的时候就真正被删除。
-当共享内存 key 为 0 的时候
	-表示共享内存被标记删除了
	-如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。不也能进行关联。

共享内存和内存映射的区别
1、共享内存可以直接创建,内存映射需要磁盘文件
2、共享内存效率更高
3、内存
所有的进程操作的是同一块共享的内存。
内存映射每个进程在自己的地址空间有一块独立的内存。
4、数据安全问题
-进程突然退出
	共享内存还存在
	内存映射区消失
-进程运行中电脑死机,宕机了
	数据存在共享内存中,没有了
	内存映射区的数据,由于磁盘映射区的文件还在,内存映射区的数据还存在,
5、生命周期
-内存映射区:进程退出,内存映射区销毁。
-共享内存:进程退出,共享内存还存在,手动删除(所有的关联进程数为0),或者关机。	
如果一个进程退出,会自动和共享内存取消关联



在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

write_shm.c

#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdio.h>
#include<string.h>
int main(void){
    //1.创建一个共享内存
    int shmid = shmget(100,4096,IPC_CREAT |0664);
    //2.和当前进程进行关联
    void*ptr = shmat(shmid,NULL,0);

    char*str = "hello world!";
    //3.向内存当中写入数据
    memcpy(ptr,str,strlen(str)+1);
//让程序暂停一下,如果程序没有暂停的话,会直接回收和销毁进程
    printf("请按任意键继续....\n");
    getchar();
    //4.解除关联
    shmdt(ptr);

    //5.删除共享内存
    shmctl(shmid,IPC_RMID,NULL);
}

在这里插入图片描述
read_shm.c

#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdio.h>
#include<string.h>
int main(void){
    //1.创建一个共享内存
    //写 0 表示去获取一个共享内存,
    //不能大于 4096
    int shmid = shmget(100,0,IPC_CREAT);
    printf("shmid : %d.\n",shmid);
    //2.和当前进程进行关联
    void*ptr = shmat(shmid,NULL,0);

    //3.读数据
    printf("%s\n",(char*)ptr);

    printf("请按任意键继续....\n");
    getchar();
    //4.解除关联
    shmdt(ptr);

    //5.删除共享内存
    shmctl(shmid,IPC_RMID,NULL);
}

在这里插入图片描述

六、内存映射
内存 映射有关的介绍
内存映射:用户将磁盘文件的数据映射到内存,用户可以通过修改内存的内容来修改磁盘文件。
最好不要使用较大的磁盘文件,较大的磁盘文件消耗内存。
父子进程当中可以使用匿名内存映射(有共享内存那味儿了),不用额外使用磁盘文件。

七、socket
使用本地的套接字文件进行内存间的通信。
本地套接字的实现和网络套接字类似,一般采用 TCP 的通信流程。
本地套接字的通信的流程:

/*服务端*/
// 本地套接字通信的流程 - tcp 
// 服务器端 
1. 创建监听的套接字 
int lfd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0); 2. 监听的套接字绑定本地的套接字文件 -> server端 
struct sockaddr_un addr; // 绑定成功之后,指定的sun_path中的套接字文件会自动生成。 
bind(lfd, addr, len); 
2. 监听
listen(lfd, 100); 
3. 等待并接受连接请求 
struct sockaddr_un cliaddr; 
int cfd = accept(lfd, &cliaddr, len); 
4. 通信接收数据:
read/recv 
发送数据:
write/send 
5. 关闭连接 close(); 

// 客户端的流程 1. 创建通信的套接字 int fd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0); 2. 监听的套接字绑定本地的IP 端口 struct sockaddr_un addr; // 绑定成功之后,指定的sun_path中的套接字文件会自动生成。 bind(lfd, addr, len); 3. 连接服务器 struct sockaddr_un serveraddr; connect(fd, &serveraddr, sizeof(serveraddr)); 4. 通信接收数据:read/recv 发送数据:write/send 5. 关闭连接 close();
// 头文件: sys/un.h 
#define UNIX_PATH_MAX 108 
struct sockaddr_un { 
sa_family_t sun_family; // 地址族协议 
af_local char sun_path[UNIX_PATH_MAX]; // 套接字文件的路径, 这是一个伪文件, 大小永远=0 
};

ipc_server.c

#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/un.h>
#include<stdlib.h>

int main(void){
    unlink("server.socket");
    //删除原先建立的套接字,不管有没有,这样就不用再次建立
    //1.创建监听的套接字
    int lfd = socket(AF_LOCAL,SOCK_STREAM,0);
    if(lfd == -1){
        perror("socket");
        exit(0);
    }
    //2.绑定本地套接字文件
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path,"server.socket"); 
   
    int ret = bind(lfd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret == -1){
        perror("bind");
        exit(0);
    }

    //3.监听
    ret = listen(lfd,100);
    if(ret == -1){
        perror("listen");
        exit(0);
    }

    //4.等待客户端连接
    struct sockaddr_un cliaddr;
    socklen_t len = sizeof(cliaddr);
    int cfd = accept(lfd,(struct sockaddr*)&cliaddr,&len);
    if(cfd == -1){
        perror("accept");
        exit(0);
    }
    printf("client socket filename %s.\n",cliaddr.sun_path);
    //5.通信
    while (1)
    {
        char buf[128];
        int len1 = recv(cfd,buf,sizeof(buf),0);
        if(len1 == -1){
            perror("recv");
            exit(0);
        }
        else if (len == 0){
            printf("client closed....\n");
            break;
        }
        else if(len > 0)
        {
            printf("client say : %s.\n",buf);
            send(cfd,buf,sizeof(buf),0);
        }
    }
    close(lfd);
    close(cfd);
    return 0;
}

ipc_client.c

#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/un.h>
#include<stdlib.h>
int main(void){
    //删除原来系统中的套接字文件
    unlink("client.socket");
    //1.创建套接字
    int cfd = socket(AF_LOCAL,SOCK_STREAM,0);
    if(cfd == -1){
        perror("socket");
        exit(0);
    }

    //2.绑定本地套接字文件
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path,"client.socket"); 
    
    int ret = bind(cfd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret == -1){
        perror("bind");
        exit(0);
    }
    //3.连接服务器
    struct sockaddr_un saddr;
    saddr.sun_family = AF_LOCAL;
    strcpy(saddr.sun_path,"server.socket");  
    ret = connect(cfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(ret == -1){
        perror("connect");
        exit(0);
    }

    //4.通信
    int num = 0;
    while (1)
    {
        //发送数据
        char buf[128];
        sprintf(buf,"hello,i am client %d.\n",num++);
        send(cfd,buf,sizeof(buf),0);
        printf("client say : %s.\n",buf);

        //接收数据
        int len1 = recv(cfd,buf,sizeof(buf),0);
        if(len1 == -1){
            perror("recv");
            exit(0);
        }
        else if (len1 == 0){
            printf("server closed....\n");
            break;
        }
        else if(len1 > 0)
        {
            printf("server say : %s.\n",buf);
        }
        sleep(1);

    }
    close(cfd);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值