Linux进程间通信(2)

目录

前言:

正文 

1.命名管道 

1.1创建及使用

1.2命名管道的工作原理 

1.3命名管道与匿名管道的区别 

2.命名管道特点及应用场景 

2.1特点 

2.2场景 

3.命名管道实操 

3.1实现文件拷贝 

3.2实现进程控制 

 总结:



前言:

管道中除了匿名管道还有一个命名管道,命名管道有自己的名字,自带同步与互斥机制、数据单向流通,可以实现毫不相干的两个独立进程间通信。

正文 

1.命名管道 

让匿名管道有名字的属性,就会使其变成命名管道。

  • 结合文件系统,给匿名管道这个纯纯的内存文件分配 inode,将文件名与之构建联系,关键点在于不给它分配 Data block,因为它是一个纯纯的内存文件,是不需要将数据刷盘到磁盘中的

1.1创建及使用

 命名管道的创建可以使用函数 mkfifo 原型如下:

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

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

关于mkfifo函数

对于参数1,既可以传递绝对路径 /home/xxx/namePipeCode/fifo,也可以传递相对路径 ./fifo,当然绝对路径更灵活,但也更长

对于参数2mode_t 其实就是对 unsigned int 的封装,等价于 uint32_t,而 mode 就是创建命名管道时的初始权限,实际权限需要经过 umask 掩码计算

不难发现,mkfifomkdir 非常像,其实 mkfifo 可以直接在命令行中运行

创建一个名为 fifo 的命名管道文件

mkfifo fifo

成功解锁了一种新的特殊类型文件:p 管道文件 

 

这个管道文件也非常特殊:大小为 0,从侧面说明 管道文件就是一个纯纯的内存级文件,有自己的上限,出现在文件系统中,只是单纯挂个名而已

可以直接在命令行中使用命名管道:

echo 可以进行数据写入,可以重定向至 fifo
cat 可以进行数据读取,同样也可以重定向于 fifo
打开两个终端窗口(两个进程),即可进行通信

当然也可以通过程序实现两个独立进程 IPC

思路:创建 服务端 server客户端 client 两个独立的进程,服务端 server 创建并以 读 的方式打开管道文件,客户端 client 以 写 的方式打开管道文件,打开后俩进程可以进程通信,通信结束后,由客户端关闭 写端(服务端 读端 读取到 0 后也关闭并删除命令管道文件)

注意:

  • 当管道文件不存在时,文件会打开失败,因此为了确保正常通信,需要先运行服务端 server 创建管道文件
  • 服务端启动后,因为是读端,所以会阻塞等待 客户端(写端)写入数据
  • 客户端写入数据时,因为 '\n' 也被读取了,所以要去除此字符
  • 通信结束后,需要服务端主动删除管道文件

 公共资源 common.h

#pragma once

#include <iostream>
#include <string>

std::string fifo_name = "./fifo";   //管道名
uint32_t mode = 0666;   //权限

服务端 server.cc

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "common.h"

using namespace std;

int main()
{
    // 服务端
    // 1、创建命名管道文件
    int ret = mkfifo(fifo_name.c_str(), mode);
    if (ret < 0)
    {
        cerr << "mkfifo fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 2、以读的方式打开文件
    int rfd = open(fifo_name.c_str(), O_RDONLY);
    if (rfd < 0)
    {
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 3、读取数据
    while (true)
    {
        char buff[64];
        int n = read(rfd, buff, sizeof(buff) - 1);
        buff[n] = '\0';

        if (n > 0)
        {
            cout << "Server get message# " << buff << endl;
        }
        else if (n == 0)
        {
            cout << "写端关闭,读端读取到0,终止读端" << endl;
            break;
        }
        else
        {
            cout << "读取异常" << endl;
            break;
        }
    }

    close(rfd);
    unlink(fifo_name.c_str());  //删除命名管道文件

    return 0;
}

 客户端 client.cc

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "common.h"

using namespace std;

int main()
{
    // 客户端
    // 1、打开文件
    int wfd = open(fifo_name.c_str(), O_WRONLY);
    if (wfd < 0)
    {
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 2、写入数据,进行通信
    char buff[64] = {0};
    while (true)
    {
        cout << "Client send message# ";
        fgets(buff, sizeof(buff) - 1, stdin);
        buff[strlen(buff) - 1] = '\0'; // 去除 '\n'

        if (strcasecmp("exit", buff) == 0)
            break;

        write(wfd, buff, strlen(buff));
    }

    close(wfd);

    return 0;
}

 注:strcasecmp 是一个字符串比较函数,无论字符串大小写,都能进行比较

1.2命名管道的工作原理 

 把视角拉回文件系统:当重复多次打开同一个文件时,并不会费力的打开多次,而且在第一次打开的基础上,对 struct file 结构体中的引用计数 ++,所以对于同一个文件,不同进程打开了,看到的就是同一个 

具体例子:

显示器文件(stdout)只有一个吧,是不是所有进程都可以同时进行写入?
同理,命名管道文件也是如此,先创建出文件,在文件系统中挂个名,然后让独立的进程以不同的方式打开同一个命名管道文件,比如进程 A 以只读的方式打开,进程 B 以只写的方式打开,那么此时进程 B 就可以向进程 A 写文件,即 IPC

 

 

因为命名管道适用于独立的进程间 IPC,所以无论是读端和写端,进程 A、进程 B 为其分配的 fd 是一致的,都是 3

  • 如果是匿名管道,因为是依靠继承才看到同一文件的,所以读端和写端 fd 不一样

1.3命名管道与匿名管道的区别 

  • 匿名管道只能用于具有血缘关系的进程间通信;而命名管道不讲究,谁都可以用
  • 匿名管道直接通过 pipe 函数创建使用;而命名管道需要先通过 mkfifo 函数创建,然后再通过 open 打开使用
  • 出现多条匿名管道时,可能会出现写端 fd 重复继承的情况;而命名管道不会出现这种情况

2.命名管道特点及应用场景 

2.1特点 

  • 管道是半双工通信
  • 管道生命随进程而终止
  • 命名管道任意多个进程间通信
  • 管道提供的是流式数据传输服务
  • 管道自带 同步与互斥 机制

2.2场景 

  1. 管道为空时,读端阻塞,等待写端写入数据
  2. 管道为满时,写端阻塞,等待读端读取数据
  3. 进程通信时,关闭读端,OS 发出 13 号信号 SIGPIPE 终止写端进程
  4. 进程通信时,关闭写端,读端读取到 0 字节数据,可以借此判断终止读端

3.命名管道实操 

3.1实现文件拷贝 

 我们可以利用 命名管道实现不同进程间 IPC,即进程从文件中读取并写入一批数据,另一个进程一次读取一批数据并保存至新文件中,这样就实现了文件的拷贝

 目标:利用命名管道,向空文件 target.txt 中写入数据,即拷贝源文件 file.txt

公共资源 common.h 

 

#pragma once

#include <iostream>
#include <string>

std::string fifo_name = "./fifo";   //管道名
uint32_t mode = 0666;   //权限

服务端(写端) server.cc 提供文件拷贝服务

 

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "common.h"

int main()
{
    //服务端 
    // 打开文件
    int wfd = open(fifo_name.c_str(), O_WRONLY);
    if(wfd < 0)
    {
        std::cerr <<"open fail! errno:"<< errno << "| "<<strerror(errno)<<std::endl;
        exit(0);
    }
    //打开源文件
    FILE *fp = fopen("file.txt","r");
    if(fp == NULL)
    {
         std::cerr <<"Fopen fail! errno:"<< errno << "| "<<strerror(errno)<<std::endl;   
         exit(0);
    }
    //读取数据
    char buff[1024] ;
    int n = fread(buff,sizeof(char),sizeof(buff),fp);

    //  IPC
    //将源文件写入命名管道
    write(wfd,buff,strlen(buff));
    std::cout << "服务端已向管道写入: " << n << "字节的数据" <<std:: endl;


    fclose(fp);
    fp = nullptr;
    close(wfd);
    return 0;
}

客户端(读端) client.cc 从服务端中拷贝文件(下载)

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "common.h"

int main()
{
   //创建管道文件
   int ret = mkfifo(fifo_name.c_str(),mode);
   if(ret<0)
   {
     std::cerr << "mkfifo fail! errno: " << errno << " | " << strerror(errno) << std::endl;
        exit(0);
   }

   //打开管道文件
   int rfd = open(fifo_name.c_str(),O_RDONLY);
   if(rfd<0)
   {
       std::cerr << "open fail! errno: " << errno << " | " << strerror(errno) << std::endl;
        exit(0);
   }

   // 打开目标文件
   FILE *fp = fopen("target.txt","w");
   if(fp == NULL)
   {
        std::cerr << "fopen fail! errno: " << errno << " | " << strerror(errno) << std::endl;
        exit(0);
   }

   // 进行拷贝 ipc-communication
   char buff[1024];
   int n = read(rfd,buff,sizeof(buff)-1);
   buff[n] = '\n';
   if (n > 0)
        std::cout << "客户端已从管道读取: " << n << "字节的数据" << std::endl;
    else if (n == 0)
       std:: cout << "写端关闭,读端读取到0,终止读端" <<std:: endl;
    else
        std::cout << "读取异常" << std::endl;


   fwrite(buff,sizeof(char),strlen(buff),fp);
   std::cout << "客户端已成功从服务端下载(拷贝)了文件数据" <<std:: endl;
   //关闭管道/文件
   fclose(fp);
   fp = nullptr;
   close(rfd);
   unlink(fifo_name.c_str());

    return 0;
}

 Makefile 文件

.PHONY:all
all:client server


server:server.cc
	g++ -o $@ $^ -std=c++11
client:client.cc
	g++ -o $@ $^ -std=c++11


.PHONY:clean
clean:
	rm -f client server

 运行结果如下图所示:

此时 服务端是写端,客户端是读端,实现的是 下载服务;当 服务端是读端,客户端是写端时,实现的就是 上传服务,搞两条管道就能模拟实现简单的 数据双向传输服务

注意: 创建管道文件后,无论先启动读端,还是先启动写端,都要阻塞式的等待另一方进行交互

3.2实现进程控制 

 

在 Linux 匿名管道 IPC 中,我们实现了一个简易版的进程控制程序,原理是通过多条匿名管道实现父进程对多个子进程执行任务分配

匿名管道用于有血缘关系间 IPC命名管道也可以

所以我们可以把上一篇文章中的 匿名管道换为命名管道,一样可以实现通信

 task.hpp

#include <iostream>
#include <string>
#include <functional>
#include <unordered_map>
#include <unistd.h>

using namespace std;

void PrintLOG()
{
    cout << "PID: " << getpid() << " 正在执行打印日志的任务…" << endl;
}

void InsertSQL()
{
    cout << "PID: " << getpid() << " 正在执行数据库插入的任务…" << endl;
}

void NetRequst()
{
    cout << "PID: " << getpid() << " 正在执行网络请求的任务…" << endl;
}

class Task
{
public:
    Task()
    {
        // 装载任务
        _tt = {{"打印日志", PrintLOG}, {"数据库插入", InsertSQL}, {"网络请求", NetRequst}};
    }

    // 展示任务
    void showTask()
    {
        cout << "目前可用任务有:[";
        for (auto e : _tt)
            cout << e.first << " ";
        cout << "]" << endl;
        cout << "输入 退出 以终止程序" << endl;
    }

    // 执行任务
    void Execute(const string &task)
    {
        if (_tt.count(task) == 0)
        {
            cerr << "没有这个任务:" << task << endl;
        }
        else
        {
            _tt[task](); // 函数对象调用
        }
    }

private:
    unordered_map<string, function<void(void)>> _tt;
};

 控制程序 namePipeCtrl.cc 包括进程、管道创建,任务执行与进程等待

 

#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Task.hpp"

using namespace std;

enum
{
    NAME_SIZE = 64
};

// 子进程基本信息类
class ProcINfo
{

public:
    ProcINfo(pid_t pid = pid_t(), int wfd = int())
        : _pid(pid), _wfd(wfd), _num(_cnt++)
    {
        char buff[NAME_SIZE] = {0};
        snprintf(buff, NAME_SIZE, "Process %d | pid:wfd [%d:%d]", _num, _pid, _wfd);
        _name = string(buff);
    }

    pid_t _pid;
    int _wfd;
    int _num;
    string _name;
    static int _cnt;
};

int ProcINfo::_cnt = 0;

// 进程控制类
class ProcCtrl
{
public:
    ProcCtrl(int num = 3, mode_t mode = 0666)
        : _num(num), _mode(mode)
    {
        // 根据 _num 创建命名管道及子进程
        CreatPipeAndProc();
    }

    ~ProcCtrl()
    {
        waitProc();
    }

    // 创建管道及进程
    void CreatPipeAndProc()
    {
        // 因为是继承的,所以也要注意写端重复继承问题
        vector<int> fds;
        for (int i = 0; i < _num; i++)
        {
            // 步骤:创建管道,存入 _vst
            char pipeNameBUff[NAME_SIZE]; // 管道名缓冲区
            snprintf(pipeNameBUff, NAME_SIZE, "./fifo-%d", i);

            int ret = mkfifo(pipeNameBUff, _mode);
            assert(ret != -1);
            (void)ret;

            _vst.push_back(string(pipeNameBUff));

            // 创建子进程,让子进程以只读的方式打开管道文件
            pid_t id = fork();
            if (id == 0)
            {
                // 子进程内
                // 先关闭不必要的写端
                for (auto e : fds)
                    close(e);

                // 打开管道文件,并进入任务等待默认(读端阻塞)
                int rfd = open(_vst[i].c_str(), O_RDONLY);
                assert(rfd != -1);
                (void)rfd;

                waitCommand(rfd);

                close(rfd); // 关闭读端
                exit(0);
            }

            // 父进程以写打开管道,保存 fd 信息
            int wfd = open(_vst[i].c_str(), O_WRONLY);
            assert(wfd != -1);
            (void)wfd;

            // 注册子进程信息
            _vpt.push_back(ProcINfo(id, wfd));
            fds.push_back(wfd);
        }
    }

    // 子进程等待任务派发
    void waitCommand(int rfd)
    {
        while (true)
        {
            char buff[NAME_SIZE] = {0};
            int n = read(rfd, buff, sizeof(buff) - 1);

            buff[n] = '\0';

            if (n > 0)
            {
                Task().Execute(string(buff));
            }
            else if (n == 0)
            {
                cerr << "读端读取到 0,写端已关闭,读端也即将关闭" << endl;
                break;
            }
            else
            {
                cerr << "子进程读取异常!" << endl;
                break;
            }
        }
    }

    // 展示可选进程
    void showProc()
    {
        cout << "目前可用进程有:[";
        int i = 0;
        for (i = 0; i < _num - 1; i++)
            cout << i << "|";
        cout << i << "]" << endl;
    }

    // 下达任务给子进程
    void ctrlProc()
    {
        while (true)
        {
            cout << "==========================" << endl;
            int n = 0;
            do
            {
                showProc();
                cout << "请选择子进程:> ";
                cin >> n;
            } while (n < 0 || n >= _num);

            Task().showTask();
            string taskName;
            cout << "请选择任务:> ";
            cin >> taskName;

            if (taskName == "退出")
                break;

            // 将信息通过命名管道写给子进程
            cout << "选择进程 ->" << _vpt[n]._name << " 执行 " << taskName << " 任务" << endl;
            write(_vpt[n]._wfd, taskName.c_str(), taskName.size());
            sleep(1);
        }
    }

    // 关闭写端、删除文件、等待子进程退出
    void waitProc()
    {
        for (int i = 0; i < _num; i++)
        {
            close(_vpt[i]._wfd);               // 关闭写端
            unlink(_vst[i].c_str());           // 关闭管道文件
            waitpid(_vpt[i]._pid, nullptr, 0); // 等待子进程
        }

        cout << "所有子进程已回收" << endl;
    }

private:
    vector<ProcINfo> _vpt; // 子进程信息表
    vector<string> _vst;   // 命名管道信息表
    int _num;              // 子进程数/命名管道数
    mode_t _mode;          // 命名管道文件的权限
};

int main()
{
    ProcCtrl p1;
    p1.ctrlProc();
    return 0;
}

关于 父子进程间使用命名管道通信 值得注意的问题:

在命名管道创建后,需要先创建子进程,让子进程打开【读端或写端】,然后才让父进程打开【写端或读端】,这是因为假如先让父进程打开【写端或读端】,那么此时父进程就会进入【阻塞】状态,导致无法创建子进程,自然也就无法再打开【读端或写端】;所以正确做法是先让子进程打开,即使子进程【阻塞】了,父进程也还能运行。不要让【阻塞】阻碍子进程的创建
子进程继承都存在的问题:写端重复继承,因此需要关闭不必要的写端 fd

 总结:

命名管道部分还有很多,结合bash 等,等后续补充。

 

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

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

相关文章

解锁财务信任,掌握企业业务合作中的倾听艺术

企业在经营管理过程中&#xff0c;经常会思考如何才能成为一个完美的财务业务融合体&#xff0c;实现业务合作的最大价值。当我们置身于企业战略规划的构建过程中&#xff0c;就会明显的感觉到&#xff0c;获得财务信任有助于指导团队做出重大决策并推动企业未来的行动。市场和…

读人工不智能:计算机如何误解世界笔记04_数据新闻学

1. 计算化和数据化的变革 1.1. 每一个领域都在进行计算化和数据化的变革 1.1.1. 出现了计算社会科学、计算生物学、计算化学或其他数字人文学科 1.1.2. 生活已走向计算化&#xff0c;人们却一点也没有变 1.2. 在如今的计算化和数据化世界中&#xff0c;调查性新闻的实践必须…

图神经网络的背后:HOW POWERFUL ARE GRAPH NEURAL NETWORKS?

PNACONV - 知乎这是一篇非常接地气的paper&#xff0c;也具有很好的应用价值。 1 aggregators文中提到不同的聚合函数实际上反应的信息是不同的&#xff0c;gin中也有提到类似的内容。 风浪&#xff1a;图神经网络的背后&#xff1a;HOW POWERFUL ARE GRAPH NEURAL NETWORK…ht…

自学Python第十五天-常用的HTML解析工具:bs4、xpath、re

自学Python第十五天-常用的HTML解析工具&#xff1a;bs4、xpath、re BS4安装和引入开始使用find_all() 方法获取标签find() 方法获取标签select() 方法获取标签&#xff0c;css 选择器从标签中获取数据 XPathxpath 基础xpath 语法规则lxml 模块xpath() 方法 REmatch() 方法sear…

【黑马程序员】STL之stack与queue常用操作

stack容器 stack基本概念 stack是一种先进后出的数据结构&#xff0c;它只有一个出口 栈中只有顶端元素才可以被外界使用&#xff0c;因此栈不支持遍历操作 stack常用接口 stack代码示例 #include <iostream> #include <stack>using namespace std;void test()…

AI:138-开发一种能够自动化生成艺术品描述的人工智能系统

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带关键代码,详细讲解供大家学习,希望…

【软件工具】Typora 免费版下载安装Markdown编辑器-Win10快速安装

目录 下载安装激活配置图片路径管理高亮显示自动保存 使用教程 前言 Typora&#xff0c; 是一款好用的编辑器和阅读器。这里为大家找了一个可使用版本&#xff0c;安装过程十分简单&#xff0c;亲测有效&#xff0c;不浪费大家时间&#xff0c;现在将Typora分享给大家免费使用。…

C# OpenVino Yolov8 Pose 姿态识别

目录 效果 模型信息 项目 代码 下载 效果 模型信息 Model Properties ------------------------- date&#xff1a;2023-09-07T17:11:43.091306 description&#xff1a;Ultralytics YOLOv8n-pose model trained on /usr/src/app/ultralytics/datasets/coco-pose.yaml a…

visual stdio 使用ATL简单使用COM组件

先试用visual stdio创建ATL项目 选择第一个创建ATL简单对象 ProgId也需要添加一下&#xff0c;默认创建完之后添加方法 STDMETHODIMP AddNumber(LONG __num, LONG* result);添加定义 STDMETHODIMP_(HRESULT __stdcall) CATLSimpleObject::AddNumber(LONG __num, LONG* r…

智能充电桩案例分析——交流充电桩

随着电动汽车的发展&#xff0c;充电桩也成为当下的一个很热门的工业产品。我们初步接触充电桩&#xff0c;有了点滴的感受。 先简单说说容易一点的交流充电桩。就是通过市电&#xff08;220V,50赫兹&#xff09;给电动汽车提供充电的能源来源。很容易理解&#xff0c;交流…

高数考研 -- 公式总结(更新中)

1. 两个重要极限 (1) lim ⁡ x → 0 sin ⁡ x x 1 \lim _{x \rightarrow 0} \frac{\sin x}{x}1 limx→0​xsinx​1, 推广形式 lim ⁡ f ( x ) → 0 sin ⁡ f ( x ) f ( x ) 1 \lim _{f(x) \rightarrow 0} \frac{\sin f(x)}{f(x)}1 limf(x)→0​f(x)sinf(x)​1. (2) lim ⁡…

产品经理学习-产品运营《什么是SOP》

目录 什么是SOP 如何执行SOP 执行SOP的重点 什么是SOP SOP就是项目流程操作的说明书 日常工作中的例行操作&#xff1a; 例行操作是指&#xff0c;在每一天&#xff0c;针对每一个用户&#xff0c;在每个项目之中&#xff0c;都必须完成的操作&#xff0c;这些必须完成的操…

CleanMyMac2024永久免费mac电脑版本安装包下载

CleanMyMac 4 for Mac&#xff1a;细致入微的功能介绍&#xff0c;CleanMyMac 4 for Mac作为一款系统清理和优化工具&#xff0c;提供了丰富而细致的功能&#xff0c;旨在满足Mac用户在不同场景下的清理和优化需求。以下是对CleanMyMac 4功能的更加细化介绍&#xff1a; CleanM…

MySQL的SQL语句

1.MySQL连接 连接命令一般是这样写的 mysql -h$ip -P$port -u$user -p比如:mysql -h127.0.0.1 -P3306 -uroot -p -h 指定连接的主机地址&#xff1b;-P 指定连接端口号&#xff1b;-u 指定用户名 -p指定用户名密码 2.SQL分类 DDL(Data Definition Language) 数据定义语言&…

【数据结构】链表面试题

203.移除链表元素 206.反转链表 876.链表的中间结点 牛客.链表中倒数第k个结点 21.合并两个有序链表 牛客.链表分隔 牛客.链表的回文结构 160.相交链表 141.环形链表 142.环形链表2 1. 移除链表元素 题目描述 思路&#xff1a; 定义一个指针cur遍历整个链表&#xff0c;一个ta…

大开眼界的4款黑科技软件,功能强大,网友:越用越上瘾

作为一名热衷于探索软件的搞机爱好者&#xff0c;小蛙在各大软件论坛间游走&#xff0c;旨在帮助大家在纷繁复杂的Windows软件世界中&#xff0c;寻找到那些真正值得安装的神器。 在忙碌的现代生活中&#xff0c;我们的磁盘空间和时间都显得尤为宝贵&#xff0c;没必要下一些鸡…

解决内嵌帆软报表出现重定向问题

最近收到反馈&#xff0c;某些程序的前端通过iframe标签内嵌finebi帆软报表时&#xff0c;出现一系列问题。 问题1: 如下图所示&#xff0c;单点登录(单点登录地址schema是https)后service地址的schema协议是http, 浏览器内核的安全策略不允许http访问https。 解决方案&#xf…

java数据结构与算法刷题-----LeetCode530. 二叉搜索树的最小绝对差

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 解题思路&#xff1a;时间复杂度O(n)&#xff0c;空间复杂度O(n) 一个有序…

阻抗与电气模型(三)

阻抗的定义为电压与电流的比值&#xff0c;通常用大写字母Z表示。Z V/I&#xff0c;当信号沿互连传播时&#xff0c;将不断的探测互连的阻抗&#xff0c;并做出相应的反应。阻抗又称为交流&#xff08;AC&#xff09;电阻。 如果知道了互连的阻抗和传播时延&#xff0c;也就知…

如何改变.net托管的入口main函数

有小伙伴问: .NET托管入口Main函数可以修改成别的函数&#xff0c;用来作为程序的入口吗&#xff1f; 答案&#xff1a;当然是可以的。这也算是.NET里面非常简单的骚操了。本篇来用最新的.NET8演示下&#xff0c;如何修改Main入口。 1.简单控制台例子&#xff1a; namespace…