Qt 地震剖面图(或者叫地震摆动图,波形变面积图)_地震剖面图怎么看-程序员宅基地

技术标签: QT随笔  Qt  QCustomPlot  地震剖面图  地震  地震图  

0: 项目需求

近期项目有了新的需求, 需要根据地震数据绘制出对应图表, 关于这种图的资料比较少, 翻了不少网站, 也没找到太多有用的数据, 而关于Qt的, 更是只有一篇论文. 不过搜这么多资料也不是一无所获, 最起码知道了这种图的名字. 如标题所示, 下文统一称其为地震剖面图.

1: 图形分析: 

上图是我查资料时找到的一张地震剖面图的图片, 可以看出,横轴代表通道, 纵轴代表时间, 图表中的折线按照则代表了震动的强度和方向(这一点说的可能不准确),  震动起来的部分,超出某个值的, 则将波峰染成黑色, 波谷则不做处理

2: Qt效果展示

在继续分析之前, 先看下用Qt实现的效果

 

3: 图形分解

如第一张图所示,   图中的折线, 坐标轴等, 可以用QCustomplot来实现. 关于波峰染色,  QCustomPlot在使用时, 如果设置了Brush笔刷, 那么其实就自带染色, 只不过效果可能与我们的需求有差异.

下面是一个简单的折线图, 然后加上笔刷 截图, 可以看到, 箭头指向的部分, 就是我们需要的染色效果. 但是这和我们的需求其实还是有些差距

 主要有2点

1)  这里的染色是以0为基础,  颜色值从0到波形的折线图中进行填充, 并不能控制 "振幅超出某个值以后, 进行填充的效果,  并且, 地震剖面图是有很多条曲线的, 不可能都以0为起点,必然要加上偏移量显示"

2) 图形是横着的, 地震剖面图一般都是竖着, 所以这一点也需要做点调整 

4: 首先尝试现有方案

QCustomPlot中, QGraph类有一个setChannelFillGraph函数, 可以将2个图层之间的部分进行填充, 下边进行测试

1) 先创建2个图层, 并设置数据看看

    QVector<double> x, y, y2;
    for(int i=0; i<11; i++)
    {
        x.push_back(i);

        if(i%3 == 0 || i%4 == 0 || i%8 == 0)
        {
            y.push_back(25);
        }
        else
        {
            y.push_back(10);
        }

        y2.push_back(20);

    }

    //设置交互属性, 可拖动,可缩放
    customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);

    //添加图层, 并设置画笔和笔刷颜色
    customPlot->addGraph();
    customPlot->graph(0)->setPen(QPen(Qt::black));
    customPlot->graph(0)->setBrush(QColor(255, 0, 0, 50));

    //设置数据进去
    customPlot->graph(0)->setData(x, y);


    //添加图层2, 并设置画笔和笔刷颜色
    customPlot->addGraph();
    customPlot->graph(1)->setPen(QPen(Qt::red));
    customPlot->graph(1)->setBrush(QColor(0, 0, 255, 50));

    //设置数据进去
    customPlot->graph(1)->setData(x, y2);

    //图层间填充
    customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));
    
    //设置坐标轴范围
    customPlot->xAxis->setRange(0, 10);
    customPlot->yAxis->setRange(-10, 30);

    //重绘
    customPlot->replot();

运行起来, 结果是这样的

 如图所示, 比起直接设置默认笔刷, 图层间填充确实可以实现以某条线为分界, 然后以该线为中心, 进行填充( 或许这样看和直接设置笔刷没啥区别, 但是入如果把红线的笔刷透明, 或者直接把设置图层1笔刷的代码注释掉的话, 就能看出来区别了 )

//    customPlot->graph(1)->setBrush(QColor(0, 0, 255, 50));

那么,  问题来了, 是不是按照这个思路下去, 把红线下边的填充去掉, 只把红线上方的部分进行填充. 就完成任务了?

我认为是不行的,  使用这种方式, 显示一条曲线, 需要2个图层, 并且数据量也是要翻倍的. 如果只显示一条线还好说, 显示的线条数量很多的话, 必然会导致卡顿.

 5: 源码分析

现有代码无法满足需求, 那么就只能对QCustomPlot进行二次开发了. 想要解决这个问题的第一步, 就是要找到, 笔刷填充部分的绘制逻辑

进入到QCustomplot.cpp源码中, 查找QCPGraph的 draw函数

void QCPGraph::draw(QCPPainter *painter)
{
    if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
    if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
    if (mLineStyle == lsNone && mScatterStyle.isNone()) return;

    QVector<QPointF> lines, scatters; // line and (if necessary) scatter pixel coordinates will be stored here while iterating over segments

    // loop over and draw segments of unselected/selected data:
    QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
    getDataSegments(selectedSegments, unselectedSegments);
    allSegments << unselectedSegments << selectedSegments;
    for (int i=0; i<allSegments.size(); ++i)
    {
        bool isSelectedSegment = i >= unselectedSegments.size();
        // get line pixel points appropriate to line style:
        QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted(-1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getLines takes care)
        getLines(&lines, lineDataRange);

        // check data validity if flag set:
#ifdef QCUSTOMPLOT_CHECK_DATA
        QCPGraphDataContainer::const_iterator it;
        for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
        {
            if (QCP::isInvalidData(it->key, it->value))
                qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
        }
#endif
        //注释的还是比较清除的, 在这里进行了图形填充的绘制
        // draw fill of graph:
        if (isSelectedSegment && mSelectionDecorator)
            mSelectionDecorator->applyBrush(painter);
        else
            painter->setBrush(mBrush);
        painter->setPen(Qt::NoPen);
        drawFill(painter, &lines);

        // draw line:
        if (mLineStyle != lsNone)
        {
            if (isSelectedSegment && mSelectionDecorator)
                mSelectionDecorator->applyPen(painter);
            else
                painter->setPen(mPen);
            painter->setBrush(Qt::NoBrush);
            if (mLineStyle == lsImpulse)
                drawImpulsePlot(painter, lines);
            else
                drawLinePlot(painter, lines); // also step plots can be drawn as a line plot
        }

        // draw scatters:
        QCPScatterStyle finalScatterStyle = mScatterStyle;
        if (isSelectedSegment && mSelectionDecorator)
            finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
        if (!finalScatterStyle.isNone())
        {
            getScatters(&scatters, allSegments.at(i));
            drawScatterPlot(painter, scatters, finalScatterStyle);
        }
    }

    // draw other selection decoration that isn't just line/scatter pens and brushes:
    if (mSelectionDecorator)
        mSelectionDecorator->drawDecoration(painter, selection());
}

上边的代码段是QCPGraph的draw函数内容, 如注释所示, 绘制填充的地方, 在drawFill函数中

那么我们进入到drawFill函数中看一下 

void QCPGraph::drawFill(QCPPainter *painter, QVector<QPointF> *lines) const
{
    if (mLineStyle == lsImpulse) return; // fill doesn't make sense for impulse plot
    if (painter->brush().style() == Qt::NoBrush || painter->brush().color().alpha() == 0) return;

    applyFillAntialiasingHint(painter);
    const QVector<QCPDataRange> segments = getNonNanSegments(lines, keyAxis()->orientation());
    //如果没有设置与目标图层绘图的话, 就绘制一个从曲线到坐标轴的0位置的闭合图形
    if (!mChannelFillGraph)
    {
        // draw base fill under graph, fill goes all the way to the zero-value-line:
        foreach (QCPDataRange segment, segments)
            painter->drawPolygon(getFillPolygon(lines, segment));

    }
    else         //如果设置了目标图层填充, 那就绘制从当前图层到目标图层填充的闭合图形
    {
        // draw fill between this graph and mChannelFillGraph:
        QVector<QPointF> otherLines;
        mChannelFillGraph->getLines(&otherLines, QCPDataRange(0, mChannelFillGraph->dataCount()));
        if (!otherLines.isEmpty())
        {
            QVector<QCPDataRange> otherSegments = getNonNanSegments(&otherLines, mChannelFillGraph->keyAxis()->orientation());
            QVector<QPair<QCPDataRange, QCPDataRange> > segmentPairs = getOverlappingSegments(segments, lines, otherSegments, &otherLines);
            for (int i=0; i<segmentPairs.size(); ++i)
                painter->drawPolygon(getChannelFillPolygon(lines, segmentPairs.at(i).first, &otherLines, segmentPairs.at(i).second));
        }
    }
}

上边的代码段我加了注释

mChannelFillGraph 变量是从哪来的, 我们可以看一下

void QCPGraph::setChannelFillGraph(QCPGraph *targetGraph)
{
    // prevent setting channel target to this graph itself:
    if (targetGraph == this)
    {
        qDebug() << Q_FUNC_INFO << "targetGraph is this graph itself";
        mChannelFillGraph = nullptr;
        return;
    }
    // prevent setting channel target to a graph not in the plot:
    if (targetGraph && targetGraph->mParentPlot != mParentPlot)
    {
        qDebug() << Q_FUNC_INFO << "targetGraph not in same plot";
        mChannelFillGraph = nullptr;
        return;
    }
    
    /* 
        没错, 我们之前测试的时候, 进行图层间填充, 调用了 setChannelFillGraph 函数, 而这个函数
        最终会修改 mChannelFillGraph 的值
    */ 
    mChannelFillGraph = targetGraph;
}

如图所示, 如果我们调用了图层间填充的设置函数,  drawFill函数, 就会进入到 else 选项中, 否则就是绘制从曲线到坐标轴0线的填充, painter->drawPolygon(), 这个函数是QPainter对象的绘制多边形的函数, 在这里我们暂且跳过, 我们需要关注的 是 getFillPolygon, 从名字上可以看出, 这个函数返回一个将要被填充的多边形范围

那么我们进入到 getFillPolygon 函数看看

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
        return QPolygonF();
      QPolygonF result(segment.size()+2);
      result[0] = getFillBasePoint(lineData->at(segment.begin()));
      std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);
      result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
      result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
      return result;
}

getFillPolygon 函数, 通过传递进来的折线图的顶点列表, 再结合 getFillBasePoint 函数获取基线坐标(基线坐标指的是 X轴为水平坐标轴的情况下,  坐标轴 Y轴为0, X轴最左边和X轴最右边, 获取到的2个坐标). 这也就解释了为什么图形填充为什么总是填充到0

那么我们需要动的地方, 就在这里了, 有折线图的顶点坐标列表, 我们就能根据自己的需求, 实现一个我们想要的填充多边形

为了验证我们的猜想, 这里先进行一下测试, 直接返回一个固定形状, 看下是否会绘制出来

注意, 需要把 之前设置的图层间填充的代码屏蔽掉

// customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));

修改getFillPolygon函数如下

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    //        if (segment.size() < 2)
//            return QPolygonF();
//        QPolygonF result(segment.size()+2);
//        result[0] = getFillBasePoint(lineData->at(segment.begin()));


//        std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);


//        result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
//        result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
//        return result;

        //这里我们直接返回一个三角形
        QPolygonF result;
        result.append(QPointF(150, 50));
        result.append(QPointF(50, 150));
        result.append(QPointF(250, 150));
        return result;


}

然后编译运行, 结果如下所示

 可以看到, 和我们预想的一致, 确实绘制出来了一个三角形.

既然这样, 那我们完全可以按照顶点数据, 重新组组装出来一个多边形结构, 让它将超出我们设置的阈值的数据部分进行填充, 低于该值的则不填充.  

理论可行, 接下来进行修改

再次回到源码部分, 

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
        return QPolygonF();
      QPolygonF result(segment.size()+2);
      result[0] = getFillBasePoint(lineData->at(segment.begin()));
      
      //这里把顶点坐标复制到了多边形顶点坐标中, 这里我们做点处理
      std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);
      
result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
      result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
      return result;
}

代码中的std::copy那一句, 把折线图的顶点坐标复制到了result容器中, result容器,正是保存填充色的容器, 我们就在复制这一步, 做点东西.

既然是超出阈值的才进行染色, 那么不难想到, 我们把数据低于阈值的, 全部设置成和阈值相等,  然后把基线也提升到和阈值相等,  是不是就行了? 

事实上,  如果只这么操作的话,  是会有点问题的, 先看下效果

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    if (segment.size() < 2)
            return QPolygonF();
        QPolygonF result(segment.size()+2);
//        result[0] = getFillBasePoint(lineData->at(segment.begin()));


//        std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);

        //计算出染色基准线, 这里把阈值设置为20, 也就是说, Y轴数据超出20的部分进行染色
        double divding = 20;
        QPointF start((*lineData)[0].x(), mValueAxis->coordToPixel(divding));
        QPointF end((*lineData).last().x(), mValueAxis->coordToPixel(divding));

        //基线起点
        result[0] = start;

        double divdingPix = mValueAxis->coordToPixel(divding);
        for(int i=0; i<lineData->size(); i++)
        {
            if((*lineData)[i].y() <= divdingPix)            //注意, lineData里边保存的是折线图的图像坐标, 左上角是0,0, 而不是我们图形中的左下角是 0,0
            {
                result.push_back((*lineData)[i]);
            }
            else
            {
                //所有低于阈值的, 都设置成阈值
                result.push_back(QPointF((*lineData)[i].x(), divdingPix));
            }
        }

        //基线终点
        result[result.size()-1] = end;



//        result.push_back(getFillBasePoint(lineData->at(segment.end()-1)));
//        result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
        return result;
}

将基线位置, 和多边形生成逻辑进行修改, 最终效果如下图

可以看到, 低于阈值的数据确实没了,  但是图好像塌下来了, 其实这个也好理解, 我们把低于阈值的数据全部设置的和阈值相等,  所以只有Y轴被提上去了,  但是折线从阈值线上跨切过去的点并没有被添加到多边形顶点坐标中

所以在这之前, 还需要有一步, 就是把X轴的位置找出来, 也就是从阈值线上跨切过去的坐标

分析出来了原因, 那就继续往下走,  把这些跨切坐标找到, 并添加到多边形顶点坐标中

修改之后的代码如下所示

//此函数返回一个形状, 依靠这个形状进行绘图
const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
{
    //闭合区间第一个点: QPointF(40.2451,542)

            //使用交点检测方式插入数据
            if (segment.size() < 1)
                return QPolygonF();
            QPolygonF result(1);

            //计算出染色基准线
            double  divding = 20;     
            QPointF start((*lineData)[0].x(), mValueAxis->coordToPixel(divding));
            QPointF end((*lineData).last().x(), mValueAxis->coordToPixel(divding));
                   

            //闭合区间第一个点
            result[0] = QPointF(start.x(), mValueAxis->coordToPixel(divding));

            //阈值线
            QLineF line1(start, end);
            QPointF po;
            int pointCount = 0;

            //生成一个带有跨切点的顶点坐标列表
            QVector<QPointF> out;
            for(int i=0; i<lineData->size()-1; i++)
            {
                //使用QPointF类的判断线段交点的函数寻找交点
                QLineF line2((*lineData)[i], (*lineData)[i+1]);

                out.push_back((*lineData)[i]);

                if(line2.intersects(line1, &po))
                {
                    out.push_back(po);
                    ++pointCount;
                }
            }
            out.push_back(lineData->last());          //把缺失的最后一个点补上


            //这里遍历带有跨切点的像素坐标列表, 同时对数据大于限定值的数据进行限制
            for(int i=0; i<out.size(); i++)
            {
                //这里比较的是像素, 因此坐标轴上小的值,在这里反而会比较大
                if(out[i].y() > start.y())
                {
                    //如果数据值小于阈值线, 那就设置其和阈值线相等
                    result.push_back(QPointF(out[i].x(), start.y()));
                }
                else
                {
                    result.push_back(out[i]);
                }

            }
                
            //打印一下顶点数量和交点数量
            qDebug() << u8"总的点数:" << result.size() << u8"交点数量:" << pointCount;


            //闭合区间最后一个点
            result.push_back(end);
            return result;
}

上边的代码片中, 使用QPointF类的 intersects 函数, 找到了线段的交点, 然后将这个交点也加入到临时顶点坐标中. 然后对临时顶点坐标进行遍历, 并将小于阈值的数据修正到和阈值相等.

进行完了这一步之后,  输出就比较接近我们的需求了

调试输出打印:

总的点数: 19 交点数量: 7

 不考虑调试输出的话, 效果似乎还不错, 但是, 调试输出显示, 顶点数量有19个, 但我们的折线图中, 其实是没这么多顶点的,  而导致顶点数增加的原因, 就在于小于阈值的点, 我们把它移动到了阈值的Y轴位置,  但这一步其实可以省略. 因为有了最左侧和最右侧的蓝色圈位置的顶点, 就已经可以决定填充色多边形的走向了. 因此, 可以把上边代码中的小于阈值部分, 移动到阈值部分的代码注释掉, 直接丢弃这个顶点坐标

//这里比较的是像素, 因此坐标轴上小的值,在这里反而会比较大
if(out[i].y() > start.y())
{
     //数据值小于阈值线, 那就完全可以丢弃了, 这里就不往顶点数据里边加了, 直接注释掉
     //result.push_back(QPointF(out[i].x(), start.y()));
}
else
{
    result.push_back(out[i]);
}

再次运行一下代码,  显示的结果一样, 但是顶点数量就少了

调试输出打印

总的点数: 14 交点数量: 7

可以看到, 相比原来的19个顶点, 现在变成了14个顶点,  这少掉的5个顶点, 其实就是小于阈值的点

 这5个顶点, 从填充多边形的顶点中删除掉了, 但是由于跨切交点的存在, 所以对图形展现并没有影响, 等到数据量多起来的时候, 这一操作可以有效的省略掉大量的点.

比如我们把点数增加到100看看

总的点数: 168 交点数量: 66
总的点数: 118 交点数量: 66

仅仅100个数据点的情况下, 就少掉了50个点, 如果每一条线的点数达到几十K的时候, 这些点数对速度和内存的影响就会大起来.

写到这里, 后边其实也就没什么好说的了

把图形竖起来的话,  只需要把X轴设置成Y轴, Y轴设置成X轴就行了

customPlot->graph(0)->setKeyAxis(ui->customPlot->yAxis);
customPlot->graph(0)->setValueAxis(ui->customPlot->xAxis);

然后再加个阈值变量, 基本上就完事了

6: 效果展示

最后, 在前边的代码基础上, 添加几个图层, 加一些数据看下效果

 

 

 

 改个颜色, 就得到了标题图

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zh15237774494/article/details/118753006

智能推荐

R之data.table -melt/dcast(数据合并和拆分)-程序员宅基地

文章浏览阅读627次。R之data.table -melt/dcast(数据拆分和合并)写在前面:数据整形的过程确实和揉面团有些类似,先将数据通过melt()函数将数据揉开,然后再通过dcast()函数将数据重塑成想要的形状reshape2包:melt-把宽格式数据转化成长格式。cast-把长格式数据转化成宽格式。(dcast-输出时返回一个数据框。acast-输出时返回一个向量/矩阵/数组。)..._data.table dcast

用正则表达式以及用Jsoup框架来解析网站_jsoup 正则-程序员宅基地

文章浏览阅读371次。1.首先得写一个爬网站的方法然后在查看输出的源代码,用正则表达式中的断言来截取_jsoup 正则

使用神经网络对黄金期货交割价格进行预测-2 MATLAB_rmse sumsqr-程序员宅基地

文章浏览阅读754次。上一篇文章介绍了数据的预处理部分,这一篇文章将会介绍神经网络模型的建立以及相关参数设计。 对于BP神经网络的模式识别来说,参数的设置对于神经网络的识别性能有着很大的影响。对于不同的问题来说应该有着其适当的参数设置。我的参数设置如下代码。%%%bp神经网络的参数设置NodeNum=12;%隐层节点数TypeNum=1;%输出节点数Epochs=500;%最大学习次数net=newff_rmse sumsqr

win11电脑亮度调节失效解决经验分享_电脑右下角亮度调不了-程序员宅基地

文章浏览阅读1.2w次,点赞12次,收藏55次。某一天电脑突然亮度变成最大,电脑键盘自带调节亮度按钮不管用;查看右下角设置栏,只有声音调节条,没有亮度调节;打开设备--系统--屏幕发现也没有亮度调节条_电脑右下角亮度调不了

stm32f091标准外设工程建立(MDK)_stm32f091 程序-程序员宅基地

文章浏览阅读102次。该芯片是参考野火f407工程建立。_stm32f091 程序

数据结构之:简简单单学会栈_swust oj1042c语言版-程序员宅基地

文章浏览阅读2.7k次,点赞3次,收藏6次。学一样东西首先要要明白学它有什么用。那么问题来了:栈使用来干么的?先说点无聊但是很必要的东西:简单来说:栈是一种数据结构,在计算机术语中是十分重要的。因为栈在 计算机中的应用很多。其中最重要的是应用于函数的调用,也经常用作临时性数据的存储。栈又名堆栈,实质上是一种线性表。只不过栈作为一种线性表是很特殊的存在。因为它的运算受到了限制:只能在表头进行插入或者删除的操作。如果你是初学者只需要_swust oj1042c语言版

随便推点

Microsoft SQL Server数据库部署过程-程序员宅基地

文章浏览阅读576次。介绍 (Introduction) Database deployments are critical tasks that can affect negative in on performance in production. In this article we’ll describe some performance related best practices for data..._sql server怎么部署数据库

利用CocoaHTTPServer实现wifi局域网传输文件到iphone_wifi分享文件工具http-程序员宅基地

文章浏览阅读6.7k次,点赞2次,收藏4次。背景近日在做一个代码阅读器,其中涉及到代码文件的上传,之前看到过许多app支持局域网传文件,因此就通过查询和研究实现了此功能,我是用的框架是CocoaHTTPServer。原理CocoaHTTPServer框架能够在iOS上建立起一个本地服务器,只要电脑和移动设备连入同一热点,即可使用电脑访问iOS服务器的页面,利用POST实现文件的上传。实现CocoaHTTPServer没有现成的向iOS设备传输_wifi分享文件工具http

什么是网络地址转换(NAT)—Vecloud 微云-程序员宅基地

文章浏览阅读246次。网络地址转换(NAT)最初在RFC1631中进行了描述。尽管最初是作为防止IPv4地址耗尽的短期解决方案提出的,但仍在使用它。NAT有什么特别之处,使网络工程师可以使用26年以上?让我们找出答案。NAT的优势NAT的最大好处是它减慢了IPv4地址空间消耗的过程。多亏了NAT,内部网络上具有分配的专用IPv4地址(RFC1918)的专用主机可以与Internet上的公用主机进行通信。换句话说,组织可以将RFC1918中定义的相同私有IPv4地址块分配给内部主机,而主机则在企业外部进行通信。由于不需要为

ubuntu18.04安装mysql8.0_乌班图18.04安装mysql8-程序员宅基地

文章浏览阅读7.2k次,点赞9次,收藏27次。1、进入mysql官网,点击No thanks, just start my download.,下载8.0版本的安装包。我的安装包名为mysql-apt-config_0.8.10-1_all.deb,下面以它为例执行命令。2、sudo dpkg -i mysql-apt-config_0.8.10-1_all.deb,然后会弹出如下窗口,确认第一项MySQL Server & Cluster后面的版本是否是8.0版本,如果不是,将光标移动到此处,enter键修改为8.0。如果是,直接向下选O_乌班图18.04安装mysql8

Linux红帽证书考试_红帽子考试版本-程序员宅基地

文章浏览阅读5k次,点赞8次,收藏40次。红帽认证工程师(RHCE,Red Hat Certified Engineer)属于Linux系统的中级水平认证,主要考核对常见服务的部署和维护能力,难度相对RHCSA认证来讲难度更大,而且要求考生必须已获得RHCSA认证。这部分的内容原本应是顶级RHCA认证中DO407科目的知识,随着考试难度的增高,认证的含金量也越高。红帽认证考试全部上机实操,一天考完,上午的RHCSA认证是两个半小时,对应的是RH124和RH134的课程内容,而下午的RHCE认证是三个半小时,对应的是RH294课程内容。_红帽子考试版本

gooflow 自定义流程图-程序员宅基地

文章浏览阅读1w次。demo链接:https://pan.baidu.com/s/1mJ46mlh8v2Q1XnZ8i5DceQ 密码:0lra注意:本地直接打开会报错。不支持 file地址: Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.at init (file:/...