0807|IO进程线程day9 IPC对象概念及示例(消息队列、共享内存、信号灯集)

 0 什么是IPC机制

概念:        

        IPC机制:Inter Process Communication,即进程间通信机制。

        进程与进程间的用户空间相互独立,内核空间共享。所以如果要实现进程间的通信,需要使用进程间通信机制。

分类(3类):

  1. 传统的进程间通信机制
        无名管道  pipe
        有名管道  fifo
        信号      signal
  2. system v操作系统的IPC对象
        消息队列  message queue
        共享内存  shared memory
        信号灯集  semaphore
  3. 可用于跨主机传输的通信
        套接字  socket

一、消息队列(message queue)

1.1 消息队列的概念

1) 消息队列的原理

   消息队列是在内核中创建一个容器(队列),进程需要将数据打包成结点,添加到队尾。或者从队列中读取结点,实现进程间通信。

2) 消息队列的特点

  1. 消息队列是面向记录的,其中消息具有特定的格式以及优先级。
  2. 消息队列总体上是根据先进先出的原则来实现读取的,也可以选择消息的类型后,按照先进先出的原则读取。
  3. 消息队列独立于进程。等进程结束后,消息队列以及其中的内容不会消失,依然存在,除非手动删除,或者重启操作系统。

3) 查看消息队列

查看消息队列: ipcs
                          ipcs -q
              
删除消息队列:ipcrm -q  msqid

1.2 消息队列的函数

1) ftok【计算键值】

功能:

        ① 该函数通过pathname提供的id,以及proj_id提供的8bit的值,计算key值(键值),给msgget shmget semget函数使用.

        ② 只要pathname和proj_id一致,则计算的key值就一致。那么通过相同key值找到的IPC对象就是同一个。

原型:

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

       key_t ftok(const char *pathname, int proj_id);

参数:

    char *pathname:文件的路径以及名字; 该文件必须存在且可访问
    int proj_id:传入一个非0参数;

返回值:

        成功,返回计算得到的key值;

        失败,返回-1,更新errno;

2) msgget【通过key在内核内存中找到对应的消息队列,并返回队列id】

功能:通过key值到内核内存中找对应的消息队列,并返回消息队列的id--->msqid;

原型:

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

       int msgget(key_t key, int msgflg);

参数:

    key_t keyftok函数返回出来的key值;
    int msgflg

  • IPC_CREAT:若消息队列不存在,则创建消息队列。若消息队列存在,则忽略该选项;
  • IPC_CREAT|0664:创建的同时指定消息队列的权限。
  • IPC_CREAT|IPC_EXCL:若消息队列不存在,则创建消息队列。若消息队列存在,则报错;

返回值:

        >=0, 成功返回消息队列的id号 msqid;

        =-1, 函数运行失败,更新errno;

3) msgsnd【打包数据发送到队列】

功能:将数据打包后发送到消息队列中;

原型:

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

       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数:

    int msqid   :指定要发送到哪个消息队列中;
    void *msgp:指定要发送的消息包的首地址;

    通用格式如下:
    struct msgbuf 
    {
        long mtype;       /* message type, must be > 0 */    消息类型,必须大于0;
        char mtext[1];    /* message data */  消息内容,类型根据需求修改,想要发什么类型就填什么类型。
                                              大小与下一个参数msgsz指定的一致
    };   

    size_t msgsz:消息内容的大小,以字节为单位。   
    int msgflg

  • 0:阻塞方式发送,当消息队列满了,则当前函数阻塞;
  • IPC_NOWAIT:非阻塞方式,当消息队列满了,该函数不阻塞,且函数运行失败,errno == EAGAIN.

返回值:

        =0, 函数运行成功;

        =-1, 函数运行失败,更新errno;

练习:将数据发送至消息队列中,当类型位0时停止输入

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct msgbuf
{
    long mtype;
    char mtext[128];
};

int main(int argc, const char *argv[])
{
    //创建消息队列
    //创建key值
    key_t key =ftok("/home/ubuntu/IO/05_IPC/04_msg/",1);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("key = %#x\n",key);

    //创建消息队列
    int msqid = msgget(key,IPC_CREAT|0664);
    if(msqid < 0)
    {
        perror("msgget");
        return -1;
    }
    printf("msqid = %d\n",msqid);

    struct msgbuf sndbuf;
    while(1)
    {
        printf("请输入消息类型 >>> ");                                        
        scanf("%ld",&sndbuf.mtype);
        getchar();
        if(0 == sndbuf.mtype)
            break;

        printf("请输入消息内容 >>> ");
        fgets(sndbuf.mtext,sizeof(sndbuf.mtext),stdin);
        sndbuf.mtext[strlen(sndbuf.mtext)-1]=0;

        //向消息队列中发送消息
        if(msgsnd(msqid,&sndbuf,sizeof(sndbuf.mtext),0) < 0)
        {
            perror("msgsnd");
            return -1;
        }
        printf("发送成功\n");
        system("ipcs -q");//让c代码执行shell命令
    }


    return 0;
}
                                                                              
                                                                              

4) msgrcv【从消息队列中读取数据】

功能:从消息队列中读取数据;

原型:

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       
       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
 int msgflg);

参数:

    int msqid   :指定要发送到哪个消息队列中;
    void *msgp:指定要发送的消息包的首地址;

    通用格式如下:
    struct msgbuf 
    {
        long mtype;       /* message type, must be > 0 */    消息类型,必须大于0;
        char mtext[1];    /* message data */  消息内容,类型根据需求修改,想要发什么类型就填什么类型。
                                              大小与下一个参数msgsz指定的一致
    };   


    size_t msgsz:消息内容的大小,以字节为单位。   
    long msgtyp :指定要读取的消息类型;

    msgtyp == 0, 读取消息队列中的第一条消息; 先进先出;
    msgtyp > 0,  指定消息类型读取,读取消息队列中第一条消息类型为 msgtyp参数指定的消息;
                 msgflg指定了MSG_EXCEPT,读取消息队列中第一条消息类型 不等于 msgtyp参数指定的消息;
                 vi -t MSG_EXCEPT;   #define MSG_EXCEPT      020000 
    msgtyp < 0,  读取消息队列中第一条最小的,且类型小于等于 msgtyp参数绝对值的消息。         

   int msgflg

        0:阻塞方式,当消息队列中没有消息了,该函数阻塞; 

        IPC_NOWAIT:非阻塞方式运行,当消息队列中没有消息了,该函数不阻塞,运行失败,errno == ENOMSG;

返回值:

        >0, 成功读取到的字节数;

        =-1, 函数运行失败,更新errno;

代码示例:

若消息队列中有消息:
    mtype 100   101  99   100  101
    mtext aaa   bbb  ccc  ddd  eee
i. msgtyp == 0
    while(1)                                                                   
    {
        //阻塞方式读取消息队列中第一条消息,先进先出的原则
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 0, 0);
        
        //非阻塞方式读取消息队列中第一条消息,先进先出的原则
        res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 0, IPC_NOWAIT);
        if(res < 0)
        {
            perror("msgrcv");
            return -1;
        }
        printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
    }
​
输出顺序:
    100 aaa   101 bbb   99 ccc   100 ddd    101 eee
ii. msgtyp > 0
    while(1)
    {
        //1.阻塞方式读取消息队列中第一条消息类型 == 101 的消息
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, 0);
​
        //2.非阻塞方式读取消息队列中第一条消息 == 101 的消息
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, IPC_NOWAIT);
                                                                                  
        //3.非阻塞方式读取消息队列中第一条消息类型 != 101的消息 
        res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, IPC_NOWAIT|020000);
        if(res < 0)
        {
            perror("msgrcv");
            return -1;
        }
        printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
    }
注释1,2的现象:
    101 bbb     101 eee
第3个的现象:
    100 aaa    99 ccc   100 ddd  
iii. msgtyp
    while(1)
    {
        //阻塞方式读取消息队列中<=msgtyp参数指定的消息类型中最小的那条消息;
        res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), -100, 0);
       
        //非阻塞方式读取消息队列中<=msgtyp参数指定的消息类型中最小的那条消息;
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), -100, IPC_NOWAIT);
​
        if(res < 0)
        {
            perror("msgrcv");
            return -1;
        }
        printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
    }
​现象:
res=128 : 99 ccc
res=128 : 100 aaa
res=128 : 100 ddd
​

5) msgctl【控制消息队列,用于删除消息队列】

功能:控制消息队列,常用于删除消息队列;

原型:

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

       int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数:

        ​​​​​​int msqid   :指定要控制的消息队列的id号;
        int cmd

    IPC_STAT:获取消息队列的属性,属性存储在第三个参数中;
    IPC_SET:设置消息队列属性,属性存储在第三个参数中;
    IPC_RMID:删除消息队列,第三个参数无效,填NULL即可;

返回值:

        成功,返回0;

        失败,返回-1,更新errno

常用示例:

    //删除消息队列
    if(msgctl(msqid, IPC_RMID, NULL) < 0)
    {
        perror("msgctl");
        return -1;
    }
    printf("删除消息队列成功\n");

6) 作业

1. 要求用消息队列实现AB进程对话

  • A进程先发送一句话给B进程,B进程接收后打印
  • B进程再回复一句话给A进程,A进程接收后打印
  • 重复1.2步骤,当收到quit后,要结束AB进程

2. 实现随时收发:用多进程 多线程。

二、共享内存(shared memory)

2.1 共享内存的概念

1) 共享内存的原理

        在内核内存中创建一个共享内存,共享内存可以被分别映射到不同的进程的用户空间中,每个进程在各自的用户空间中就可以操作同一个共享内存,从而可以操作同一个物理地址空间。

2) 共享内存的特点

① 共享内存是 最高效的 进程间通信方式。

  • 进程可以在用户空间,通过指针直接访问共享内存,对共享内存进行读写,不需要任何的数据拷贝。

② 共享内存是在内核空间中被创建,可以被映射到不同的进程中。

③ 多个进程可以同事访问共享内存,因此对于共享内存的操作,需要引入进程的同步互斥机制:信号灯集

④ 共享内存独立于进程,即使进程结束,共享内存及其中的数据依然存在,除非手动删除或者重启操作系统。

⑤ 共享内存中的数据即使被读取后,依然存在,不会被删除。

3) 查看共享内存

查看共享内存:ipcs
              ipcs -m   
删除共享内存:ipcrm -m  shmid

2.2 共享内存的函数

1) ftok【计算键值】

功能:① 该函数通过pathname提供的id,以及proj_id提供的8bit的值,计算key值(键值),给msgget shmget semget函数使用.

          ② 只要pathname和proj_id一致,则计算的key值就一致。那么通过相同key值找到的IPC对象就是同一个。

原型:

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

       key_t ftok(const char *pathname, int proj_id);

参数:

    char *pathname:文件的路径以及名字; 该文件必须存在且可访问
    int proj_id:传入一个非0参数;

返回值:

        成功,返回计算得到的key值;

        失败,返回-1,更新errno;

2) shmget【通过key在内核内存中找到对应的消息队列,并返回队列id】

功能:通过key值到内核内存中找对应的共享内存,并返回共享内存的id--->msqid;

原型:

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

       int shmget(key_t key, size_t size, int shmflg);

参数:

    key_t key ftok函数返回出来的key值;

    ssize_t 指定要申请多少个字节的共享内存;
    int shmflg

  • IPC_CREAT:若共享内存不存在,则创建共享内存。若共享内存存在,则忽略该选项;
  • IPC_CREAT|0664:创建的同时指定共享内存的权限。
  • IPC_CREAT|IPC_EXCL:若共享内存不存在,则创建共享内存。若共享内存存在,则报错;

返回值:

        >=0, 成功返回共享内存的id号 shmid;

        =-1, 函数运行失败,更新errno;

3) shmat【将共享内存映射到用户空间中】

功能:将共享内存映射到用户空间中;

原型:

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

       void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:

    int shmid   :指定要映射的共享内存id号;
    const void *shmaddr 

        指定共享内存要映射到用户空间的位置,填对应空间的首地址; 例如:(void*)0x10

        填NULL,代表让操作系统自动映射;
    int shmgflg

  • 0:默认方式映射,进程对共享内存可读可写;
  • SHM_RDONLY:只读,进程对共享内存只读;

返回值:

        成功,返回共享内存映射到用户空间的首地址;

        失败,返回 (void *) -1,更新errno;

注意:

        获取到的映射空间的首地址的指向不允许修改,若修改后会导致首地址找不到,导致内存泄漏,与堆空间首地址不能改变的概念一致

4) shmdt【断开映射】

功能:将共享内存与进程的用户空间断开映射;

           当进程不想操作共享内存的时候,就可以断开映射

原型:

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

 
       int shmdt(const void *shmaddr);

参数:

    void *shmaddr 指定要断开映射的用户空间的首地址;

返回值:

        成功,返回0;

        失败,返回-1,更新errno;

5) shmctl【控制共享内存,常用于删除共享内存】

功能:通过key值到内核内存中找对应的共享内存,并返回共享内存的id--->msqid;

原型:

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

       int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:

   ​​​​​​int msqid   :指定要控制的消息队列的id号;
   int cmd

    IPC_STAT:获取消息队列的属性,属性存储在第三个参数中;
    IPC_SET: 设置消息队列属性,属性存储在第三个参数中;
    IPC_RMID:删除消息队列,第三个参数无效,填NULL即可;

返回值:

        成功,返回0;

        失败,返回-1,更新errno;

6) 常用示例

    //删除共享内存
    if(shmctl(shmid, IPC_RMID, NULL) < 0)
    {
        perror("shmctl");
        return -1;
    }
    printf("删除共享内存成功\n");

7) 函数使用示例

写入

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    //创建key值                                             
    key_t key = ftok("./",10);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("key = %#x\n",key);

    //创建共享内存,获得shmid号
    int shmid = shmget(key, 32, IPC_CREAT|0664);
    if(shmid < 0)
    {
        perror("shmget");
        return -1;
    }
    printf("shmid = %d\n",shmid);

    //映射共享内存到用户空间
    void* addr= shmat(shmid, NULL, 0);
    if((void*)-1 == addr)
    {
        perror("shmat");
        return -1;
    }
    printf("addr = %p\n",addr);

    //先往共享内存中存储一个int类型数据
    //再在int类型数据后面存储一个字符串
    *(int*)addr=10;
    strcat((char*)addr+4,"hello world");
    //*(char*)addr+4="hello world";


    system("ipcs -m");
    return 0;
}

读取

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    //创建key值
    key_t key = ftok("./",10);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("key = %#x\n",key);

    //创建共享内存,获得shmid号
    int shmid = shmget(key, 32, IPC_CREAT|0664);
    if(shmid < 0)
    {
        perror("shmget");
        return -1;
    }
    printf("shmid = %d\n",shmid);

    //映射共享内存到用户空间
    void* addr= shmat(shmid, NULL, 0);
    if((void*)-1 == addr)
    {                                             
        perror("shmat");
        return -1;
    }
    printf("addr = %p\n",addr);

    //先往共享内存中存储一个int类型数据
    //再在int类型数据后面存储一个字符串
    printf("%d",*(int*)addr);
    printf("%s\n",(char*)addr+4);

    system("ipcs -m");
    return 0;
}

结果:

 

8) 作业

1.要求在共享内存中存入字符串 “1234567”。A进程循环打印字符串,B进程循环倒置字符串,要求结果不允许出现乱序:

        提示:共享内存中存储 flag + string.

三、信号灯集(semaphore)

3.1 信号灯集的概念

1)信号灯集的原理

2)信号灯集的核心操作

3.2 信号灯集的函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/63243.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

物联网潜在的巨大价值在于大数据分析

物联网潜在的巨大价值在于大数据分析 从数据里去挖掘市场或者用户的精准需求。 往小的说&#xff0c;后台可以统计用户家里各各插座一年甚至更久的用电情况&#xff0c;这些数据也可以通过app或者小程序展现给用户。 用户可以很直观看到自己一年的用电情况&#xff0c;哪个家…

【Grafana】中文界面配置 v10.0.3

比如通过 docker run -d -p 3000:3000 -v /e/code/monitor/grafana/grafana.ini.txt:/etc/grafana/grafana.ini grafana/grafana运行一个容器&#xff08;最新是v10.0.3&#xff09;。 在 /admin/settings 可以看到 users 部分有一个 default_language 配置。 所以在挂载到 …

读取文件和写入文件操作

在java中会涉及到对文件进行读取和写入操作&#xff0c;以下将介绍如何用java对文件进行读取和写入 读取 通过Readr读取字符流文件中的数据 读取字符流文件中的数据表示以字符为单位进行读取 package 文件操作;import java.io.*;/*** Created with IntelliJ IDEA.* Descript…

Docker 容器化学习

文章目录 前言Docker架构 1、 docker安装2、启动docker服务3、设置docker随机器一起启动4、docker体验5、docker常规命令5.1、容器操作docker [run|start|stop|restart|kill|rm|pause|unpause]docker [ps|inspect|exec|logs|export|import] 5.2、镜像操作docker images|rmi|tag…

深入了解 PostgreSQL 扩展插件

深入了解 PostgreSQL 扩展插件 在 PostgreSQL 数据库中&#xff0c;扩展插件是极具价值的工具&#xff0c;它们为我们提供了丰富多样的功能增强。本篇博客将深入介绍几个常用的 PostgreSQL 扩展插件&#xff0c;包括 pg_stat_statements、uuid、postgis 以及 postgis_raster。…

vector模拟实现

vector模拟实现 构造函数拷贝构造函数析构函数赋值运算符重载容量大小相关的函数size()capacity()reserveresize 修改容器内容相关函数push_backpop_backinserteraseswap 访问容器内容相关函数operator[] 与迭代器相关函数begin()和end()关于迭代器失效的问题 构造函数 vector…

【JVM】垃圾回收 ——自问自答2

Q: System.gc() 的理解 System.gc()底层调用的是 Runtime.getRuntime.gc(),会现实出发FullGC。 但是&#xff0c;它的调用附带一个免责声明&#xff0c;无法保证对垃圾收集器的调用。 Q&#xff1a; 内存溢出和内存泄漏&#xff1f; 内存溢出&#xff1a; 简而言之&#xf…

删除这4个文件夹,流畅使用手机无忧

在现代社会中&#xff0c;手机已经成为我们生活中不可或缺的一部分。然而&#xff0c;随着使用时间的增长&#xff0c;我们可能会遇到手机卡顿和内存不足的问题&#xff0c;让我们感到十分困扰。手机卡顿不仅影响使用体验&#xff0c;还可能导致应用程序运行缓慢&#xff0c;甚…

3分钟白话RocketMQ系列—— 核心概念

白话3分钟&#xff0c;快速了解RocketMQ基础&#xff0c;包括适用场景&#xff0c;以及基本概念。 看完如果不了解&#xff0c;欢迎来打我。 关键字摘要 低延迟、高可用、高可靠、高并发 的消息中间件适合在线业务分为producer、consumer、nameserver、broker等角色另外还有主…

第3章 语言基础

引言 任何语言的核心所描述的都是这门语言在最基本的层面上如何工作&#xff0c;涉及语法、操作符、数据类型以及内置功能&#xff0c;在此基础之上才可以构建复杂的解决方案 本章接下来的内容主要基于ECMAScript第6版。ES6 语法 js的语法借鉴了c/c&#xff0c;java。js是相对…

快速消除视频的原声的技巧分享

网络上下载的视频都会有视频原声或者背景音乐&#xff0c;如果不喜欢并且想更换新的BGM要怎么操作呢&#xff1f;今天小编就来教你如何快速给多个视频更换新的BGM&#xff0c;很简单&#xff0c;只需要将原视频的原声快速消音同时添加新的背景音频就行&#xff0c;一起来看看详…

c语言函数作为形参的注意事项

1、c语言数组作为形参会退化成数组指针 #include "stdio.h" #include <stdlib.h>//数组作为函数的形参会退化成指针 void print_arr(int a[5]);// int *b int main() {int arr[5] { 1,2,3,4,5 };print_arr(arr);return 0; }void print_arr(int a[5]) {printf…

UVA1347 旅行 Tour (样例解释 + 思路心得 + 代码)

目录 题目大意 样例解释 第一组样例解释 第二组样例解释 有注释的代码 没有注释的代码 题目大意 样例解释 &#xff08;多组样例&#xff09; 第一组样例解释 第二组样例解释 &#xff08;没有注释的代码实际上很短 在后面&#xff09; 有注释的代码 思路和心得都在代码里, 快…

【Linux】计算机网络的背景和协议分层

文章目录 网络发展协议何为协议网络协议协议分层OSI七层模型TCP/IP五层模型&#xff08;四层&#xff09; 基本通信流程mac地址和ip地址网络通信本质 网络发展 从一开始计算机作为一台台单机使用&#xff0c;到现在网络飞速发展&#xff0c;从局域网Lan建立起局域网&#xff0…

git bash 安装sdkadmin

1.下载相关安装包,复制到git 安装目录 D:\software\Git\mingw64\bin 2. 运行 curl -s "https://get.sdkman.io" | bash

Service not registered 异常导致手机重启分析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、Service not registered 异常导致手机重启二、Service not registered 解决方案 一、Service not registered 异常导致手机重启 1.重启 的部分Log如…

某大型医院门户网站性能分析案例

故障现象 门户网站近期出现少量的访问体验慢现象&#xff0c;主要是由于服务器响应时间慢。出现慢页面的页面簇为&#xff1a;http://www.xxx.ac.cn/。 分析过程 下面将分析异常原因:页面的URL信息&#xff1f;页面慢的原因&#xff1f; 性能问题分析&#xff0c;定位到慢访…

不可错过的家装服务预约小程序商城开发指南

在当今社会&#xff0c;家装行业发展迅速&#xff0c;越来越多的人开始寻求专业的家装预约和咨询服务。对于不懂技术的新手来说&#xff0c;创建一个自己的家装预约咨询平台可能听起来很困难&#xff0c;但实际上通过一些第三方制作平台和工具&#xff0c;这个过程可以变得简单…

ES6 数组的用法

1. forEach() 用来循环遍历的 for 数组名.forEach(function (item,index,arr) {})item:数组每一项 , index : 数组索引 , arr:原数组作用: 用来遍历数组 let arr [1, 2, 3, 4]; console.log(arr); let arr1 arr.forEach((item, index, arr) > {console.log(item, index…

客服型电话呼叫中心系统,助力企业提升客户服务质量

客服型电话呼叫中心系统是企业客户服务的重要工具之一&#xff0c;它通过电话和网络等方式&#xff0c;为客户提供快速、便捷、高效的服务。客服型电话呼叫中心系统具备自动接听来电、自动路由、管理知识库、录音和监控、生成报表分析等多种功能&#xff0c;有利于企业提高客户…