【Linux进程通信】二、匿名管道

Ⅰ. 管道一、管道的概念

​ 管道是 unix 中最古老的进程间基于文件系统通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个 “管道”。注意管道是单向连通的,不存在说双向管道,就像生活中水往低处流而不会往高处流一样!

【Linux进程通信】二、匿名管道

​ 进程 A 通过管道将数据写入到 “公共内存” 中,并且进程 B 可以从该段 “公共内存” 中读取这些数据,这样子的话就达到了两个进程之间的交互!

​ 那么有人可能会有问题:既然这段 “公共内存” 是共享的并且都是基于文件系统的,那这个管道文件是不是在磁盘上面呢,然后进程A通过写入到磁盘中的管道文件,进程B再去读取这样子的方式 ❓❓❓

​ 其实不是的,因为我们都知道,文件 IO 的效率是相当的低的,所以操作系统肯定不笨,操作系统会将这个管道文件 load 到内存中,也就是内存级别的文件,而我们两个进程之间只需要和这个内存级文件打交道即可,这样子大大的提高了效率!

​ 任何一个文件包括两套资源:

struct file 的操作方法有属于自己的内核缓冲区,所以父进程和子进程有一份公共的资源:文件系统提供的内核缓冲区,父进程可以向对应的文件的文件缓冲区写入,子进程可以通过文件缓冲区读取,此时就完成了进程间通信,这种方式提供的文件称为管道文件。管道文件本质就是内存级文件,不需要 IO

​ 管道的本质是内核中的缓冲区,通过内核缓冲区实现通信,管道文件只是一个标识符,用于让多个进程能够访问同一块缓冲区,并非通信介质。

​ 并且通过这个内存级别文件的不同形式,我们分为匿名管道和命名管道,后面我们来一一介绍!

二、管道通信原理

​ 通过我们上面所说的,管道是个 内存级别的文件(struct file 中特有的,就像内核缓冲区一样),我们要知道为什么它会叫做 “管道” 呢,是因为它一开始就叫做 “管道” 吗,那肯定不是,是因为它的原理和 “管道” 是类似的,人们后期才会将其命名为 “管道”,而管道是单通向的,不存在双向管道!

​ 并且我们生活中常见的管道比如说水管、油等等它们的作用就是来传输水和油,那么我们计算机中的管道就是传输数据,那么这个流向肯定要是一条单向的,我们就不能想象出其原理结构:

【Linux进程通信】二、匿名管道

​ 除此之外,我们可以再说说普通文件,我们父进程创建子进程,子进程拷贝父进程的文件描述符表,所以都指向 log 文件,而每次我们写入完毕之后,可能会存在写满等情况就会刷新,那么会访问磁盘,每次读取时候又将 log 加载进内存,不断的来回,这样子 IO 次数非常的多,效率也就非常的低,所以说为什么存在管道文件,其实就是为了避开普通文件通信的效率底下问题!

【Linux进程通信】二、匿名管道

Ⅱ. 匿名管道一、匿名管道的原理与创建方法

​ 在讲匿名管道之前呢,我们必须知道管道它的通信方法,而对于匿名管道来说,其实就是 通过 fork 创建子进程!(而命名管道的方法不需要创建子进程,后面会讲)

​ 为什么对于匿名管道来说需要创建子进程呢 ❓❓❓

​ 匿名管道的名称由来也是因为这个原因,我们需要通过 fork 创建子进程,我们都知道,子进程会继承父进程的大部分属性和内容包括文件描述符(若发生写时拷贝则会改变),也就是说父进程指向的文件 file,子进程也会指向同一个文件 file(父子进程文件描述符表是独立的,但是指向的文件是同一个),而就像我们下图,父子进程可以都指向该管道文件,这样子的话我们就无需说让子进程和父进程去专门创建一个带名称的管道并且指向它,也就是说我们可以 利用父子进程的继承性让子进程继承这个管道文件达到共同指向它的目的,所以这个管道文件没必要带名称,所以叫做匿名管道!

​ 所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了 “Linux 一切皆文件思想“!

【Linux进程通信】二、匿名管道

​ 那么我们如何创建这个匿名管道文件呢 ❓❓❓

​ 下面就得调用我们的系统函数 pipe()

代码语言:javascript代码运行次数:0运行复制

#include int pipe(int pipefd[2]);// 功能:创建一无名管道// 参数:pipefd为文件描述符数组,其中fd[0]表示读端,fd[1]表示写端// 返回值:成功返回0,失败返回-1且设置错误代码

​ 大家是不是就会很疑惑这个 pipefd[2] 数组里面放的是固定的值吗,其实不是的,它会根据当前进程打开文件的个数也就是文件描述符表中 fd_array[] 的有效个数进行返回,但是我们能确定的就是其中 fd[0] 表示的是读,fd[1] 表示的是写,而不需要我们关心具体的 fd 是多少!

​ 假设我们的进程没有打开文件,也就是说只打开了默认的三个标准输入输出流,那么自然我们的 fd[0] = 3fd[1] = 4,但如果我们开了一个文件,那么 fd[0] = 4fd[1] = 5,所以说我们关系的是 fd[0]fd[1] 而不是具体的 3 还是 4

​ 知道了如何创建匿名管道文件,下面我们来看看如何建立起父子进程之间的通信,也就是让他们看到同一份资源!下面假设我们让父进程进行写入,而子进程进行读取来模拟过程:

首先我们要创建一个父进程,并让这个父进程创建并指向这个管道文件接着父进程 fork 创建子进程, 父子进程同时指向管道文件(子进程拷贝父进程大部分内容属性),并且 fd[0]fd[1] 都是打开的然后父进程关闭读端也就是 fd[0],子进程关闭写端也就是 fd[1],这样子的话就形成了一条单向的管道!

【Linux进程通信】二、匿名管道

​ 有人会问能不能不关父进程的读端等等,答案肯定是不行的,因为不关的话可能在读写的时候会发生一些问题,所以 不是我们要的管道通向是必须关的!

​ 至于在通信完毕之后,要不要将父子进程中对应的管道关闭呢,这个是建议关闭的,因为不关闭的话,怕会误操作等等!

二、管道读写特征

​ 首先我们先打印一个没有手动打开文件的进程,看看它对应的 fd[0]fd[1] 是多少:

代码语言:javascript代码运行次数:0运行复制

#include #include #include using namespace std;int main(){    int fds[2];     int n = pipe(fds);    assert(n != -1);    // fds[0]:读    // fds[1]:写    cout << "fds[0]: " << fds[0] << endl;    cout << "fds[1]: " << fds[1] << endl;    return 0;}
【Linux进程通信】二、匿名管道

​ 和我们上面的判断一样!

​ 接下来我们来根据上面讲的匿名管道原理,根据通信的步骤,完成父子进程的通信!(下面以子进程写入,父进程读取为例)

① 读取快,写入慢的情况

​ 这里所谓的读取快,写入慢其实就是子进程写入的时候让他先 sleep 一会再写入:

代码语言:javascript代码运行次数:0运行复制

#include #include #include #include #include #include #include #include using namespace std;int main(){    // 1、创建管道文件,打开读写端    int fds[2];     int n = pipe(fds);    assert(n == 0);    // 2、创建子进程    pid_t id = fork();    assert(id >= 0);    if(id == 0)    {        // 子进程的通信代码        close(fds[0]); // 子进程关掉读端        int cnt = 0;        const char* msg = "i am child, sending msg now!"; // 子进程要发的信息        while(true)        {            char buffer[1024]; // 只有子进程能看到                        // snprintf只是sprintf加上了写入个数,将格式化内容转化为字符串            snprintf(buffer, sizeof(buffer), "child msg: %s[%d][%d]", msg, cnt++, getpid());            write(fds[1], buffer, strlen(buffer)); // 不需要算入''               sleep(1); // 每隔一秒写一次,这是这个情况的关键点          }        close(fds[1]); // 建议最后关闭子进程写端        cout << "子进程关闭自己的写端" < 0)        {            buffer[n] = '';            cout << "Get msg# " << buffer << " parent_pid: " << getpid() << endl;        }    }    // 回收子进程    n = waitpid(id, NULL, 0);    assert(n == id);    close(fds[0]); // 建议最后关掉读端    return 0;}

​ 下面写个监控脚本:

代码语言:javascript代码运行次数:0运行复制

while :; do ps ajx | head -1 && ps ajx | grep a.out ; sleep 1 ; done          
【Linux进程通信】二、匿名管道

​ 可以看到每 sleep 一秒后父进程接收一次信息,如果我们将 sleep 变成五秒,那么其实父进程会一直处于阻塞状态(监控脚本中看到的 S+ 状态),等待着子进程写入数据,才会响应!也就是说,如果管道中没有写入数据,那么读取的那一端默认会进入阻塞状态!

​ 为了验证一下,我们在父进程读取数据那行代码前后打印一些信息来看看:

代码语言:javascript代码运行次数:0运行复制

cout << "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" << endl;ssize_t n = read(fds[0], buffer, sizeof(buffer) - 1); // 多留一个位置放cout << "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" << endl;
【Linux进程通信】二、匿名管道

② 读取慢,写入快的情况

​ 这个情况和情况①是相反的,也就是让父子进程睡眠的顺序交换一下,当父进程在读取的时候让它 sleep 上一会,然后再读取,而子进程不停的写入,整体代码是不变的:

【Linux进程通信】二、匿名管道
【Linux进程通信】二、匿名管道

​ 可以看到,在父进程睡眠的五秒期间,子进程其实在不断的写入,所以每隔五秒接收子进程的信息的时候,就是一堆的数据!

​ 并且我们可以将父进程的睡眠时间调久一点,不让父进程去接收,看看让子进程不断的写入数据,我们可以在子进程中打印一下 cnt 的大小看看是怎么变化的,最后会不会这段管道文件被填满:

【Linux进程通信】二、匿名管道
【Linux进程通信】二、匿名管道

​ 可以看出来,管道文件是有固定大小的,是会被写满的!侧面说明 写入端写满的时候,写入端会阻塞,等待读取端读取!

③ 写入端提前关闭的情况

​ 这里让写入端提前关闭,也就是这里子进程我们让它打印一遍后就直接 break,接着它就会关掉自己的写入端,此时父进程也就是读取端,调用 read 接收的时候,返回值为 0,所以我们可以加以判断后退出。(注意,如果写入端没有被关闭,即使没有写入,那么读取端用 read 的时候也不会返回 0 的!)

【Linux进程通信】二、匿名管道
【Linux进程通信】二、匿名管道

​ 也就是说,写入端提前退出,读取端会读到返回值0!

④ 读取端提前关闭的情况

​ 操作系统很聪明,对于这种读取端提前关闭的情况,操作系统会给写入端进程发送信号,终止写入端,因为没人在接收,那么这只是在浪费系统资源,下面我们用 status 来接受一下子进程的终止信号看看效果:

【Linux进程通信】二、匿名管道

三、总结读写规则

通过上面的几种情况,我们可以看到管道读写的几种规则(额外补充一些):

当没有数据可读时 O_NONBLOCK disable:read 调用阻塞,即进程暂停执行,一直等到有数据来到为止O_NONBLOCK enable:read 调用返回 -1errno 值为 EAGAIN当管道满的时候 O_NONBLOCK disable: write 调用阻塞,直到有进程读走数据O_NONBLOCK enable:调用返回 -1errno 值为 EAGAIN

如果所有管道写入端对应的文件描述符被关闭,则 读取端 read 返回 0 如果所有管道读取端对应的文件描述符被关闭,则 write 操作会产生信号 SIGPIPE ,进而 终止写入端 write 当要写入的数据量不大于 PIPE_BUF 时,linux 将保证写入的原子性。 当要写入的数据量大于 PIPE_BUF 时,linux 将不再保证写入的原子性。 Ⅲ. 管道的特征

1、一般而言,进程退出,管道释放,所以 管道的生命周期随进程。

2、管道用来进行具有血缘关系的进程之间进行通信,常用于父子通信。

3、管道是 面向字节流 的。(网络)

4、管道是 半双工 的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。

5、一般而言,内核会对管道操作进行同步与互斥,这是一种对共享资源进行保护的方案。

6、管道本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信

【Linux进程通信】二、匿名管道

Ⅳ. 自己写一个匿名管道池

​ 所谓的匿名管道池,其实就是我们的父进程也就是当前进程,有一组子进程等待父进程调度,读取父进程写入的信号进行某些工作的完成,这个就是匿名管道池!池化技术应用也是非常广泛的,这里我们试着写一个小demo!

​ 大概的设计思路就是如下,一个父进程通过创建各自的管道文件对各自的子进程分别管理起来!

【Linux进程通信】二、匿名管道

​ 首先我们需要先创建一个管道进程池,其实就是一个数组,其中数组每个元素都是一个结构体,结构体中需要包括每个对应管道文件的写入端文件描述符、子进程的 pid 等等。

​ 可能会有人想为什么不加上那个管道文件的读取端文件描述符呢 ❓❓❓

​ 其实是因为我们在到时候在子进程执行命令那部分代码上面,会调用 pipe() 产生匿名管道,而下面的子进程是能轻易的拿到这个读取端的文件描述符,而对于父进程,它需要管理多个子进程,所以我们需要将每个写端文件描述符记录起来,以便管理!

​ 具体的实现看下面的代码(其中细节挺多,主要从 main 函数切入,并且在 CreateProcessPool 函数也就是创建管道池中存在 bug,需要我们修复,就是每次产生的子进程会拷贝父进程的读写端,如果有多个子进程的话,那么就会造成多个写入端同时指向一个子进程的情况,所以我们必须采取一些措施,具体看代码!):

完整代码:代码语言:javascript代码运行次数:0运行复制

#include #include #include #include #include #include #include #include #include #include using namespace std;#define MakeSeed() srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234)///中间部分为任务功能部分/typedef void(* func_t)(); // 函数指针void IOTask(){    cout << getpid() << " : IO Task runningn" << endl;    sleep(1);}void DownloadTask(){    cout << getpid() << " : Download Task runningn" << endl;    sleep(1);}void flushTask(){    cout << getpid() << " : flush Task runningn" << endl;    sleep(1);}void LoadFunction(vector* funcMap){    assert(funcMap);    funcMap->push_back(IOTask);    funcMap->push_back(DownloadTask);    funcMap->push_back(flushTask);}///以下为管道池的管理const int PROCESS_NUM = 5; // 子进程池的个数// 描述每对子进程和管道文件的结构体class childProcess{public:    childProcess(pid_t pid, int writeFd)        :_pid(pid), _writeFd(writeFd)    {        char buffer[1024];        snprintf(buffer, sizeof(buffer), "process-%d[pid(%d)|writeFd(%d)]", num++, _pid, _writeFd);        _name = buffer;    }public:    string _name; // 以统一的规则命名的名称    pid_t _pid; // 子进程pid    int _writeFd; // 管道文件的写入端    static int num; // 当前子进程为第几个进程编号};int childProcess::num = 0;void SendTask(const childProcess& cp, int index_func){    cout << "Send task num: " << index_func < " << cp._name << endl;    // 发送任务就是向管道里写入数据    int n = write(cp._writeFd, &index_func, sizeof(index_func));    assert(n == sizeof(int)); // 断言一下,发送的字节大小必须为int类型大小    (void)n;}int ReceiveTask(int readFd){    int code = 0;    ssize_t n = read(readFd, &code, sizeof(code));    if(n == 4)         return code;     else if(n <= 0)        return -1;    else         return 0;}void CreateProcessPool(vector* pipePool, vector& funcMap){    vector deleteFd; // 记录每次要关闭的前面的子进程的写端    for(int i = 0; i = 0); // 断言一下是否fork成功        // 子进程的执行部分        if(id == 0)        {            // 每次删掉子进程拷贝父进程的前n个写入端指向            for(int i = 0; i = 0 && commandCode push_back(move(cp)); // 将该对象调用move移动构造到管道池        deleteFd.push_back(pipefd[1]); // 记录每个子进程的前n个写入端fd    }}void loadBlanceContrl(vector& cp, vector& fmp, int taskCnt){    int pipe_size = cp.size(); // 管道池个数    int func_size = fmp.size(); // 任务个数    bool forever = (taskCnt == 0 ? true : false); // 判断是否为永远    while(true)    {        // 1、选择一个子进程        int index_process = rand() % pipe_size;        // 2、选择一个任务        int index_func = rand() % func_size;        // 3、任务发送给选择的进程        SendTask(cp[index_process], index_func);        sleep(1);        if(!forever)        {            taskCnt--;            if(taskCnt == 0) break;           }    }    // 关闭写入端    for(int i = 0; i < PROCESS_NUM; ++i)        close(cp[i]._writeFd); // 类似堆栈的方式的原理关闭的写入端}void waitProcess(vector& cp){    for(int i = 0; i < PROCESS_NUM; ++i)    {        // 这里不做退出码等处理        waitpid(cp[i]._pid, nullptr, 0);        cout < " << cp[i]._pid << endl;    }}int main(){    MakeSeed(); // 随机种子    // 1、创建管道进程池和任务列表    vector funcMap;    LoadFunction(&funcMap);    vector pipePool;    CreateProcessPool(&pipePool, funcMap);    // 2、父进程控制子进程完成任务,负载均衡的向子进程发送命令码,若父进程退出则关闭子进程    int taskCnt = 3; // 0: 永远进行,其它则表示计数    loadBlanceContrl(pipePool, funcMap, taskCnt);        // 3、回收子进程信息    waitProcess(pipePool);    return 0;}

以上就是【Linux进程通信】二、匿名管道的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/74039.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月14日 01:19:13
下一篇 2025年11月14日 01:55:57

相关推荐

  • CSS mask属性无法获取图片:为什么我的图片不见了?

    CSS mask属性无法获取图片 在使用CSS mask属性时,可能会遇到无法获取指定照片的情况。这个问题通常表现为: 网络面板中没有请求图片:尽管CSS代码中指定了图片地址,但网络面板中却找不到图片的请求记录。 问题原因: 此问题的可能原因是浏览器的兼容性问题。某些较旧版本的浏览器可能不支持CSS…

    2025年12月24日
    900
  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 为什么设置 `overflow: hidden` 会导致 `inline-block` 元素错位?

    overflow 导致 inline-block 元素错位解析 当多个 inline-block 元素并列排列时,可能会出现错位显示的问题。这通常是由于其中一个元素设置了 overflow 属性引起的。 问题现象 在不设置 overflow 属性时,元素按预期显示在同一水平线上: 不设置 overf…

    2025年12月24日 好文分享
    400
  • 网页使用本地字体:为什么 CSS 代码中明明指定了“荆南麦圆体”,页面却仍然显示“微软雅黑”?

    网页中使用本地字体 本文将解答如何将本地安装字体应用到网页中,避免使用 src 属性直接引入字体文件。 问题: 想要在网页上使用已安装的“荆南麦圆体”字体,但 css 代码中将其置于第一位的“font-family”属性,页面仍显示“微软雅黑”字体。 立即学习“前端免费学习笔记(深入)”; 答案: …

    2025年12月24日
    000
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么我的特定 DIV 在 Edge 浏览器中无法显示?

    特定 DIV 无法显示:用户代理样式表的困扰 当你在 Edge 浏览器中打开项目中的某个 div 时,却发现它无法正常显示,仔细检查样式后,发现是由用户代理样式表中的 display none 引起的。但你疑问的是,为什么会出现这样的样式表,而且只针对特定的 div? 背后的原因 用户代理样式表是由…

    2025年12月24日
    200
  • inline-block元素错位了,是为什么?

    inline-block元素错位背后的原因 inline-block元素是一种特殊类型的块级元素,它可以与其他元素行内排列。但是,在某些情况下,inline-block元素可能会出现错位显示的问题。 错位的原因 当inline-block元素设置了overflow:hidden属性时,它会影响元素的…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 为什么使用 inline-block 元素时会错位?

    inline-block 元素错位成因剖析 在使用 inline-block 元素时,可能会遇到它们错位显示的问题。如代码 demo 所示,当设置了 overflow 属性时,a 标签就会错位下沉,而未设置时却不会。 问题根源: overflow:hidden 属性影响了 inline-block …

    2025年12月24日
    000
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 为什么我的 CSS 元素放大效果无法正常生效?

    css 设置元素放大效果的疑问解答 原提问者在尝试给元素添加 10em 字体大小和过渡效果后,未能在进入页面时看到放大效果。探究发现,原提问者将 CSS 代码直接写在页面中,导致放大效果无法触发。 解决办法如下: 将 CSS 样式写在一个单独的文件中,并使用 标签引入该样式文件。这个操作与原提问者观…

    2025年12月24日
    000
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 em 和 transition 设置后元素没有放大?

    元素设置 em 和 transition 后不放大 一个 youtube 视频中展示了设置 em 和 transition 的元素在页面加载后会放大,但同样的代码在提问者电脑上没有达到预期效果。 可能原因: 问题在于 css 代码的位置。在视频中,css 被放置在单独的文件中并通过 link 标签引…

    2025年12月24日
    100
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信