mongodb源码分析session接受客户端find命令过程

news2025/9/7 5:35:12

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制。

现在继续研究ASIOSession和connection是怎么接受客户端命令的?

mongo/transport/service_state_machine.cpp核心方法有:

enum class State {
        Created,     // The session has been created, but no operations have been performed yet
        Source,      // Request a new Message from the network to handle
        SourceWait,  // Wait for the new Message to arrive from the network
        Process,     // Run the Message through the database
        SinkWait,    // Wait for the database result to be sent by the network
        EndSession,  // End the session - the ServiceStateMachine will be invalid after this
        Ended        // The session has ended. It is illegal to call any method besides
                     // state() if this is the current state.
    };

void _processMessage()
void _sinkCallback()
void _sourceCallback()
void _sourceMessage()
void _sinkMessage()
void _runNextInGuard()

mongo第一条命令状态转变流程是:State::Created 》 State::Source 》State::SourceWait 》 State::Process 》 State::SinkWait  》 State::Source 》State::SourceWait

状态解释:State::Created,     //session刚刚创建,但是还没有接受任何命令
            State::Source,      //去接受客户端新的命令
            State::SourceWait,  // 等待客户端新的命令
            State::Process,     // 将接受的命令发送给mongodb数据库
           State:: SinkWait,    // 等待将命令的执行结果返回给客户端

mongo/transport/service_state_machine.cpp核心方法循环调用的流程图下:

mongo/transport/service_state_machine.cpp核心_runNextInGuard方法主要判断状态State::Source和State::Process,State::Source主要session等待客户端请求find命令State::Process将命令发送给mongodb数据库, _runNextInGuard代码如下:

void ServiceStateMachine::_runNextInGuard(ThreadGuard guard) {
    auto curState = state();
    dassert(curState != State::Ended);

    // If this is the first run of the SSM, then update its state to Source
    if (curState == State::Created) {
        curState = State::Source;
        _state.store(curState);
    }

    // Make sure the current Client got set correctly
    dassert(Client::getCurrent() == _dbClientPtr);
    try {
        switch (curState) {
            case State::Source:
				LOG(1) << "conca _runNextInGuard  State::Source" ;
                _sourceMessage(std::move(guard));
				
                break;
            case State::Process:
				LOG(1) << "conca _runNextInGuard  State::Process" ;
                _processMessage(std::move(guard));
                break;
            case State::EndSession:
				LOG(1) << "conca _runNextInGuard  State::EndSession" ;
                _cleanupSession(std::move(guard));
                break;
            default:
                MONGO_UNREACHABLE;
        }

        return;
    } catch (const DBException& e) {
        log() << "DBException handling request, closing client connection: " << redact(e);
    }

    if (!guard) {
        guard = ThreadGuard(this);
    }
    _state.store(State::EndSession);
    _cleanupSession(std::move(guard));
}

session等待connection请求,状态转变流程:State::Created 》 State::Source 》State::SourceWait,

void ServiceStateMachine::_sourceMessage(ThreadGuard guard) {
    invariant(_inMessage.empty());
    invariant(_state.load() == State::Source);
	LOG(1) << "conca _sourceMessage State::Source";
    _state.store(State::SourceWait);
	LOG(1) << "conca _sourceMessage store State::SourceWait";
    guard.release();

    auto sourceMsgImpl = [&] {
        if (_transportMode == transport::Mode::kSynchronous) {
            MONGO_IDLE_THREAD_BLOCK;
            return Future<Message>::makeReady(_session()->sourceMessage());
        } else {
            invariant(_transportMode == transport::Mode::kAsynchronous);
            return _session()->asyncSourceMessage();
        }
    };

    sourceMsgImpl().getAsync([this](StatusWith<Message> msg) {
        if (msg.isOK()) {
            _inMessage = std::move(msg.getValue());
            invariant(!_inMessage.empty());
        }
        _sourceCallback(msg.getStatus());
    });
}

 _sourceMessage主要处理状态State::Source 》State::SourceWait,等待session接受消息。_session()->asyncSourceMessage()方法session异步等待客户端发送的find命令消息。

如果有find命令到来则调用_sourceCallback(msg.getStatus());_sourceCallback方法代码如下:

void ServiceStateMachine::_sourceCallback(Status status) {
    // The first thing to do is create a ThreadGuard which will take ownership of the SSM in this
    // thread.
    ThreadGuard guard(this);

    // Make sure we just called sourceMessage();
	LOG(1) << "conca _sinkMessage State::SinkWait";
    dassert(state() == State::SourceWait);
    auto remote = _session()->remote();

    if (status.isOK()) {
        _state.store(State::Process);
		LOG(1) << "conca _sinkMessage store State::Process";

        // Since we know that we're going to process a message, call scheduleNext() immediately
        // to schedule the call to processMessage() on the serviceExecutor (or just unwind the
        // stack)

        // If this callback doesn't own the ThreadGuard, then we're being called recursively,
        // and the executor shouldn't start a new thread to process the message - it can use this
        // one just after this returns.
        return _scheduleNextWithGuard(std::move(guard),
                                      ServiceExecutor::kMayRecurse,
                                      transport::ServiceExecutorTaskName::kSSMProcessMessage);
    } else if (ErrorCodes::isInterruption(status.code()) ||
               ErrorCodes::isNetworkError(status.code())) {
        LOG(1) << "Session from " << remote
               << " encountered a network error during SourceMessage: " << status;
        _state.store(State::EndSession);
    } else if (status == TransportLayer::TicketSessionClosedStatus) {
        // Our session may have been closed internally.
        LOG(1) << "Session from " << remote << " was closed internally during SourceMessage";
        _state.store(State::EndSession);
    } else {
        log() << "Error receiving request from client: " << status << ". Ending connection from "
              << remote << " (connection id: " << _session()->id() << ")";
        _state.store(State::EndSession);
    }

    // There was an error receiving a message from the client and we've already printed the error
    // so call runNextInGuard() to clean up the session without waiting.
    _runNextInGuard(std::move(guard));
}

收到find命令转给mongodb执行find命令,状态转变:State::SourceWait》 State::Process,继续调用_scheduleNextWithGuard 》 schedule 调度 》 _runNextInGuard(上面已经存在,反复调用这个方法)。

现在_runNextInGuard方法里面状态State::Process,所以继续调用的方法是_processMessage执行mongodb数据库命令,接受mongodb数据库返回的数据,_processMessage详细代码如下:

void ServiceStateMachine::_processMessage(ThreadGuard guard) {
    invariant(!_inMessage.empty());
	LOG(1) << "conca _processMessage";
    TrafficRecorder::get(_serviceContext)
        .observe(_sessionHandle, _serviceContext->getPreciseClockSource()->now(), _inMessage);

    auto& compressorMgr = MessageCompressorManager::forSession(_session());

    _compressorId = boost::none;
    if (_inMessage.operation() == dbCompressed) {
        MessageCompressorId compressorId;
        auto swm = compressorMgr.decompressMessage(_inMessage, &compressorId);
        uassertStatusOK(swm.getStatus());
        _inMessage = swm.getValue();
        _compressorId = compressorId;
    }

    networkCounter.hitLogicalIn(_inMessage.size());

    // Pass sourced Message to handler to generate response.
    auto opCtx = Client::getCurrent()->makeOperationContext();

    // The handleRequest is implemented in a subclass for mongod/mongos and actually all the
    // database work for this request.
    DbResponse dbresponse = _sep->handleRequest(opCtx.get(), _inMessage);

    // opCtx must be destroyed here so that the operation cannot show
    // up in currentOp results after the response reaches the client
    opCtx.reset();

    // Format our response, if we have one
    Message& toSink = dbresponse.response;
    if (!toSink.empty()) {
        invariant(!OpMsg::isFlagSet(_inMessage, OpMsg::kMoreToCome));
        invariant(!OpMsg::isFlagSet(toSink, OpMsg::kChecksumPresent));

        // Update the header for the response message.
        toSink.header().setId(nextMessageId());
        toSink.header().setResponseToMsgId(_inMessage.header().getId());
        if (OpMsg::isFlagSet(_inMessage, OpMsg::kChecksumPresent)) {
#ifdef MONGO_CONFIG_SSL
            if (!SSLPeerInfo::forSession(_session()).isTLS) {
                OpMsg::appendChecksum(&toSink);
            }
#else
            OpMsg::appendChecksum(&toSink);
#endif
        }

        // If the incoming message has the exhaust flag set and is a 'getMore' command, then we
        // bypass the normal RPC behavior. We will sink the response to the network, but we also
        // synthesize a new 'getMore' request, as if we sourced a new message from the network. This
        // new request is sent to the database once again to be processed. This cycle repeats as
        // long as the associated cursor is not exhausted. Once it is exhausted, we will send a
        // final response, terminating the exhaust stream.
        _inMessage = makeExhaustMessage(_inMessage, &dbresponse);
        _inExhaust = !_inMessage.empty();

        networkCounter.hitLogicalOut(toSink.size());

        if (_compressorId) {
            auto swm = compressorMgr.compressMessage(toSink, &_compressorId.value());
            uassertStatusOK(swm.getStatus());
            toSink = swm.getValue();
        }

        TrafficRecorder::get(_serviceContext)
            .observe(_sessionHandle, _serviceContext->getPreciseClockSource()->now(), toSink);

        _sinkMessage(std::move(guard), std::move(toSink));
		LOG(1) << "conca _processMessage _sinkMessage";

    } else {
        _state.store(State::Source);
        _inMessage.reset();
		LOG(1) << "conca _processMessage store(State::Source)";
        return _scheduleNextWithGuard(std::move(guard),
                                      ServiceExecutor::kDeferredTask,
                                      transport::ServiceExecutorTaskName::kSSMSourceMessage);
    }
}

 DbResponse dbresponse = _sep->handleRequest(opCtx.get(), _inMessage)将接受的find命令发送给mongodb数据库,mongodb数据库执行逻辑,返回响应结果。

最后调用_sinkMessage,将mongodb结果数据发送给客户端。状态转变流程:State::Process 》 State::SinkWait

void ServiceStateMachine::_sinkMessage(ThreadGuard guard, Message toSink) {

    // Sink our response to the client
    invariant(_state.load() == State::Process);
	LOG(1) << "conca _sinkMessage State::Source";
    _state.store(State::SinkWait);
	LOG(1) << "conca _sinkMessage store State::SinkWait";
    guard.release();

    auto sinkMsgImpl = [&] {
        if (_transportMode == transport::Mode::kSynchronous) {
            // We don't consider ourselves idle while sending the reply since we are still doing
            // work on behalf of the client. Contrast that with sourceMessage() where we are waiting
            // for the client to send us more work to do.
            return Future<void>::makeReady(_session()->sinkMessage(std::move(toSink)));
        } else {
            invariant(_transportMode == transport::Mode::kAsynchronous);
            return _session()->asyncSinkMessage(std::move(toSink));
        }
    };

    sinkMsgImpl().getAsync([this](Status status) { _sinkCallback(std::move(status)); });
}

_session()->asyncSinkMessage(std::move(toSink))发送结果给客户端,成功之后继续调用_sinkCallback。

void ServiceStateMachine::_sinkCallback(Status status) {
    // The first thing to do is create a ThreadGuard which will take ownership of the SSM in this
    // thread.
    ThreadGuard guard(this);
	LOG(1) << "conca _sinkCallback State::SinkWait";
    dassert(state() == State::SinkWait);

    // If there was an error sinking the message to the client, then we should print an error and
    // end the session. No need to unwind the stack, so this will runNextInGuard() and return.
    //
    // Otherwise, update the current state depending on whether we're in exhaust or not, and call
    // scheduleNext() to unwind the stack and do the next step.
    if (!status.isOK()) {
        log() << "Error sending response to client: " << status << ". Ending connection from "
              << _session()->remote() << " (connection id: " << _session()->id() << ")";
        _state.store(State::EndSession);
        return _runNextInGuard(std::move(guard));
    } else if (_inExhaust) {
        _state.store(State::Process);
		LOG(1) << "conca _sinkCallback store State::Process";
        return _scheduleNextWithGuard(std::move(guard),
                                      ServiceExecutor::kDeferredTask |
                                          ServiceExecutor::kMayYieldBeforeSchedule,
                                      transport::ServiceExecutorTaskName::kSSMExhaustMessage);
    } else {
        _state.store(State::Source);
		LOG(1) << "conca _sinkCallback store State::Source";
        return _scheduleNextWithGuard(std::move(guard),
                                      ServiceExecutor::kDeferredTask |
                                          ServiceExecutor::kMayYieldBeforeSchedule,
                                      transport::ServiceExecutorTaskName::kSSMSourceMessage);
    }
}

session发完消息之后, State::SinkWait 》 State::Source 到此位置这个find命令已经完成,_scheduleNextWithGuard后面继续等待新的命令到来。

上面find命令打印日志如下: 

2025-04-24T21:24:02.580+0800 D1 NETWORK  [conn1] conca _sourceMessage State::Source
2025-04-24T21:24:02.581+0800 D1 NETWORK  [conn1] conca _sourceMessage store State::SourceWait
2025-04-24T21:24:09.957+0800 D1 NETWORK  [conn1] conca _sinkMessage State::SinkWait
2025-04-24T21:24:09.957+0800 D1 NETWORK  [conn1] conca _sinkMessage store State::Process
2025-04-24T21:24:09.958+0800 D1 NETWORK  [conn1] conca func end
2025-04-24T21:24:09.961+0800 D1 NETWORK  [conn1] conca guard.release() end
2025-04-24T21:24:09.962+0800 D1 NETWORK  [conn1] conca _runNextInGuard  State::Process
2025-04-24T21:24:09.963+0800 D1 NETWORK  [conn1] conca _processMessage
2025-04-24T21:24:09.964+0800 D1 NETWORK  [conn1] conca _processMessage _sep->handleRequest
2025-04-24T21:24:09.965+0800 I  SHARDING [conn1] Marking collection db.user as collection version: <unsharded>
2025-04-24T21:24:09.969+0800 I  COMMAND  [conn1] command db.user appName: "MongoDB Shell" command: find { find: "user", filter: {}, lsid: { id: UUID("7ae50c73-fcc6-4d15-8a80-7f2bf2192e0f") }, $db: "db" } planSummary: COLLSCAN keysExamined:0 docsExamined:6 cursorExhausted:1 numYields:0 nreturned:6 reslen:421 locks:{ ReplicationStateTransition: { acquireCount: { w: 1 } }, Global: { acquireCount: { r: 1 } }, Database: { acquireCount: { r: 1 } }, Collection: { acquireCount: { r: 1 } }, Mutex: { acquireCount: { r: 1 } } } storage:{ data: { bytesRead: 416 } } protocol:op_msg 3ms
2025-04-24T21:24:09.975+0800 D1 NETWORK  [conn1] conca _processMessage _sep->handleRequest dbresponse
2025-04-24T21:24:09.978+0800 D1 NETWORK  [conn1] conca _processMessage !toSink.empty()
2025-04-24T21:24:09.978+0800 D1 NETWORK  [conn1] conca _processMessage makeExhaustMessage
2025-04-24T21:24:09.979+0800 D1 NETWORK  [conn1] conca _processMessage TrafficRecorder::get(_serviceContext) .observe
2025-04-24T21:24:09.980+0800 D1 NETWORK  [conn1] conca _processMessage _sinkMessage
2025-04-24T21:24:09.981+0800 D1 NETWORK  [conn1] conca _sinkMessage State::Source
2025-04-24T21:24:09.986+0800 D1 NETWORK  [conn1] conca _sinkMessage store State::SinkWait
2025-04-24T21:24:09.986+0800 D1 NETWORK  [conn1] conca _sinkCallback State::SinkWait
2025-04-24T21:24:09.987+0800 D1 NETWORK  [conn1] conca _sinkCallback store State::Source
2025-04-24T21:24:09.988+0800 D1 NETWORK  [conn1] conca func end
2025-04-24T21:24:09.989+0800 D1 NETWORK  [conn1] conca guard.release() end
2025-04-24T21:24:09.990+0800 D1 NETWORK  [conn1] conca_serviceExecutor->schedule(std::move(func), flags, taskName)
2025-04-24T21:24:09.991+0800 D1 NETWORK  [conn1] conca_serviceExecutor->schedule(std::move(func), flags, taskName)
2025-04-24T21:24:09.992+0800 D1 NETWORK  [conn1] conca _runNextInGuard  State::Source
2025-04-24T21:24:09.993+0800 D1 NETWORK  [conn1] conca _sourceMessage State::Source
2025-04-24T21:24:09.994+0800 D1 NETWORK  [conn1] conca _sourceMessage store State::SourceWait

总结:

mongo第一条命令流程是:State::Created 》 State::Source 》State::SourceWait 》 State::Process 》 State::SinkWait  》 State::Source 》State::SourceWait

mongo第二条命令流程是:                                                                                       State::Process 》 State::SinkWait  》 State::Source 》State::SourceWait

mongo第三条命令流程是:                                                                                       State::Process 》 State::SinkWait  》 State::Source 》State::SourceWait

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

变焦位移计:机器视觉如何克服人工疲劳与主观影响?精准对结构安全实时监测

变焦视觉位移监测与人工监测的对比 人工监测是依靠目测检查或借助于全站仪&#xff0c;水准仪&#xff0c;RTK等便携式仪器测量得到的信息&#xff0c;但是随着整个行业的发展&#xff0c;传统的人工监测方法已经不能满足监测需求&#xff0c;从人工监测到自动化监测已是必然趋…

【Axure结合Echarts绘制图表】

1.绘制一个矩形&#xff0c;用于之后存放图表&#xff0c;将其命名为test&#xff1a; 2.新建交互 -> 载入时 -> 打开链接&#xff1a; 3.链接到URL或文件路径&#xff1a; 4.点击fx&#xff1a; 5.输入&#xff1a; javascript: var script document.createEleme…

使用web3工具结合fiscobcos网络部署调用智能合约

借助 web3 工具&#xff0c;在 FISCO BCOS 网络上高效部署与调用智能合约&#xff0c;解锁区块链开发新体验。 搭建的区块链网络需要是最新的fiscobcos3.0&#xff0c;最新的才支持web3调用 现在分享踩坑经验&#xff0c;希望大家点赞 目录 1.搭建fiscobcos节点&#xff08;3.…

Oracle/openGauss中,DATE/TIMESTAMP与数字日期/字符日期比较

ORACLE 运行环境 openGauss 运行环境 0、前置知识 ORACLE&#xff1a;DUMP()函数用于返回指定表达式的数据类型、字节长度及内部存储表示的详细信息 SELECT DUMP(123) FROM DUAL; -- Typ2 Len3: 194,2,24 SELECT DUMP(123) FROM DUAL;-- Typ96 Len3: 49,50,51 -- ASCII值&am…

爬虫学习-Scrape Center spa6 超简单 JS 逆向

关卡 spa6 电影数据网站&#xff0c;无反爬&#xff0c;数据通过 Ajax 加载&#xff0c;数据接口参数加密且有时间限制&#xff0c;适合动态页面渲染爬取或 JavaScript 逆向分析。 首先抓包发现get请求的参数token有加密。 offset表示翻页&#xff0c;limit表示每一页有多少…

尚硅谷redis7 86 redis集群分片之3主3从集群搭建

86 redis集群分片之3主集群搭建 3主3从redis集群配置 找3台真实虚拟机,各自新建 mķdir -p /myredis/cluster 新建6个独立的redis实例服务 IP:192.168.111.175端口6381/端口6382 vim /myredis/cluster/redisCluster6381.conf bind 0.0.0.0 daemonize yes protected-mode no …

【解决办法】Git报错error: src refspec main does not match any.

在命令行中使用 Git 进行 git push -u origin main 操作时遇到报错error: src refspec main does not match any。另一个错误信息是&#xff1a;error: failed to push some refs to https://github.com/xxx/xxx.git.这是在一个新设备操作时遇到的问题&#xff0c;之前没有注意…

2025年5月24号高项综合知识真题以及答案解析(第1批次)

2025年5月24号高项综合知识真题以及答案解析

【NATURE氮化镓】GaN超晶格多沟道场效应晶体管的“闩锁效应”

2025年X月X日,布里斯托大学的Akhil S. Kumar等人在《Nature Electronics》期刊发表了题为《Gallium nitride multichannel devices with latch-induced sub-60-mV-per-decade subthreshold slopes for radiofrequency applications》的文章,基于AlGaN/GaN超晶格多通道场效应晶…

Ubuntu24.04换源方法(新版源更换方式,包含Arm64)

一、源文件位置 Ubuntu24.04的源地址配置文件发生改变&#xff0c;不再使用以前的sources.list文件&#xff0c;升级24.04之后&#xff0c;而是使用如下文件 /etc/apt/sources.list.d/ubuntu.sources二、开始换源 1. 备份源配置文件 sudo cp /etc/apt/sources.list.d/ubunt…

26 C 语言函数深度解析:定义与调用、返回值要点、参数机制(值传递)、原型声明、文档注释

1 函数基础概念 1.1 引入函数的必要性 在《街霸》这类游戏中&#xff0c;实现出拳、出脚、跳跃等动作&#xff0c;每项通常需编写 50 - 80 行代码。若每次调用都重复编写这些代码&#xff0c;程序会变得臃肿不堪&#xff0c;代码可读性与维护性也会大打折扣。 为解决这一问题&…

彻底理解一个知识点的具体步骤

文章目录 前言一、了解概念&#xff08;是什么&#xff09;二、理解原理&#xff08;为什么&#xff09;三、掌握方法&#xff08;怎么用&#xff09; 四、动手实践&#xff08;会用&#xff09;五、类比拓展&#xff08;迁移能力&#xff09;六、总结归纳&#xff08;融会贯通…

yolov8改进模型

YOLOv8 作为当前 YOLO 系列的最新版本&#xff0c;已经具备出色的性能。若要进一步改进&#xff0c;可以从网络架构优化、训练策略增强、多任务扩展和部署效率提升四个方向入手。以下是具体改进思路和实现示例&#xff1a; 1. 网络架构优化 (1) 骨干网络增强 引入 Transform…

<< C程序设计语言第2版 >> 练习 1-23 删除C语言程序中所有的注释语句

1. 前言 本篇文章介绍的是实现删除C语言源文件中所有注释的功能.希望可以给C语言初学者一点参考.代码测试并不充分, 所以肯定还有bug, 有兴趣的同学可以改进. 原题目是: 练习1-23 编写一个删除C语言程序中所有的注释语句. 要正确处理带引号的字符串与字符常量. 在C语言中, 注释…

Fluence (FLT) 2026愿景:RWA代币化加速布局AI算力市场

2025年5月29日&#xff0c;苏黎世 - Fluence&#xff0c;企业级去中心化计算平台&#xff0c;荣幸地揭开其2026愿景的面纱&#xff0c;并宣布将于6月1日起启动四大新举措。 Fluence 成功建立、推出并商业化了其去中心化物理基础设施计算网络&#xff08;DePIN&#xff09;&…

如何撰写一篇优质 Python 相关的技术文档 进阶指南

&#x1f49d;&#x1f49d;&#x1f49d;在 Python 项目开发与协作过程中&#xff0c;技术文档如同与团队沟通的桥梁&#xff0c;能极大提高工作效率。但想要打造一份真正实用且高质量的 Python 技术文档类教程&#xff0c;并非易事&#xff0c;需要在各个环节深入思考与精心打…

MiniMax V-Triune让强化学习(RL)既擅长推理也精通视觉感知

MiniMax 近日在github上分享了技术研究成果——V-Triune&#xff0c;这次MiniMax V-Triune的发布既是AI视觉技术也是应用工程上的一次“突围”&#xff0c;让强化学习&#xff08;RL&#xff09;既擅长推理也精通视觉感知&#xff0c;其实缓解了传统视觉RL“鱼和熊掌不可兼得”…

Hash 的工程优势: port range 匹配

昨天和朋友聊到 “如何匹配一个 port range”&#xff0c;觉得挺有意思&#xff0c;简单写篇散文。 回想起十多年前&#xff0c;我移植并优化了 nf-HiPAC&#xff0c;当时还看不上 ipset hash&#xff0c;后来大约七八年前&#xff0c;我又舔 nftables&#xff0c;因为用它可直…

HackMyVM-Dejavu

信息搜集 主机发现 ┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:39:60:4c, IPv4: 192.168.43.126 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.43.1 c6:45:66:05:91:88 …

Opencv实用操作5 图像腐蚀膨胀

相关函数 腐蚀函数 img1_erosion cv2.erode(img1,kernel,iterations1) &#xff08;图片&#xff0c;卷积核&#xff0c;次数&#xff09; 膨胀函数 img_dilate cv2.dilate(img2,kernel1,iterations1) &#xff08;图片&#xff0c;卷积核&#xff0c;次数&#xff09;…