USRP初学者使用手册(基础配置及bug记录)——Linux+Clion(单台X310收发)

文章目录


前言

由于这是实验室已经配好的电脑,所以没有硬件驱动的安装过程,不好意思啦

⚠️ 注意:单台USRP设备自收发不要用MATLAB

在Windows + MATLAB环境下,用单台USRP做同步收发时,while循环会因为TX/RX阻塞互相等待而卡死,无法通过MATLAB内部机制解决。

切换到**Linux + C++(pthread)**后,将发送和接收分别放在独立线程中,彻底解决了这个问题。如果你在做单台设备的雷达感知或loopback实验,直接用C++ + UHD库,不要在MATLAB上浪费时间。


一、配置以太网(针对X310)

1.连接设备,设置以太网静态IP

设备连接到电脑上的网口上,实验室用的万兆网口

打开电脑设置------网络------以太网(enp1s0f1 是本机连接USRP的网卡接口名)

手动配置IP为192.168.40.3,设置完重新开关 一下以太网

2.Linux环境初始化

在运行 uhd_usrp_probe 或任何UHD程序之前,先在终端执行以下命令:

bash 复制代码
# 增大网络缓冲区,防止USRP数据流丢包(over/underflow)
sudo sysctl -w net.core.rmem_max=24912805
sudo sysctl -w net.core.wmem_max=24912805
# 开启巨帧(Jumbo Frame),降低网络传输开销,适配USRP 1GigE/10GigE接口
sudo ifconfig enp1s0f1 mtu 9000

enp1s0f1 是本机连接USRP的网卡接口名,根据实际情况替换(可用 ip link 查看)。

3.检测连接

初始化后在终端执行,看到下面这样的输出即为连接成功

bash 复制代码
uhd_usrp_probe

二、Clion+Cmake入门(新建项目开始)

由于之前从未接触过c++项目,所以这部分会比较琐碎,供我以后操作使用

原始项目是GitHub的通信项目(两台N210收发)https://github.com/bmorgan5/fun_ofdm

1.理清项目结构

接手的项目往往如山一样堆积,所以从0开始理项目结构很重要

项目主要包括两个内容:

1.file_comm:发射已经写好的dat文件内容,接收后保存为dat文件,不做任何处理,交由matlab处理

2.realtime_comm:完整的发射---接收---解调---译码流程,但很简略,没有任何复杂处理,朴实无华的64点fft

x310_comm/

├── CMakeLists.txt

├── cmake/

│ └── Modules/

│ ├── FindUHD.cmake

│ └── FindFFTW3.cmake

├── common/

│ └── CMakeLists.txt

├── file_comm/

│ ├── CMakeLists.txt

│ └── file_comm_main.cpp

├── realtime_comm/

│ ├── CMakeLists.txt

│ └── realtime_comm_main.cpp

├── data/

│ ├── input/

│ └── output/

└── build/

文件夹 职责
cmake/Modules/ Find 模块,从旧项目直接复制
common/ 两个 target 共用的代码(暂为空)
file_comm/ 文件读写通信的全部源码
realtime_comm/ 实时 USRP 通信的全部源码
data/ 运行时数据文件,不参与编译
build/ cmake 构建产物,不入 git

2.项目文件初始设置

新建clion项目

在 CLion 中新建 C++ 项目,命名为 x310_comm。CLion 会自动生成一个顶层 main.cpp 和 CMakeLists.txt,把自动生成的 main.cpp 直接删掉。

建立目录结构(空文件夹)

在项目根目录下手动创建以下文件夹:

cmake/Modules/

common/

file_comm/

realtime_comm/

data/input/

data/output/

build/

复制 Find 模块

从旧项目的 cmake/Modules/ 目录,把以下两个文件直接复制到新项目的 cmake/Modules/:

FindUHD.cmake

FindFFTW3.cmake

写 CMakeLists.txt(AI辅助)

顶层 CMakeLists.txt

common/CMakeLists.txt

file_comm/CMakeLists.txt

realtime_comm/CMakeLists.txt

创建占位 main 文件

在 file_comm/ 和 realtime_comm/ 各新建对应的 .cpp 文件,内容先只写:

cpp 复制代码
int main() { return 0; }

编译验证

Cmake流程:编辑CMakeList.txt------终端cmake------在Clion中点文件重新加载Cmake项目

进入空的build文件夹---右键在终端打开,执行以下命令

bash 复制代码
cmake ..
make

看到以下输出说明骨架配置成功:

3.编辑file_comm部分

(1)函数文件

file_comm_main.cpp函数和CMakeLists.txt如附录所示,不多赘述

(2)数据文件

需要把matlab生成的发送帧文件(tx_waveform.dat)放在data文件夹中

然后就可以直接运行得到接收端data了!保存为rx_result.dat,便于拿回matlab处理

运行是在右上角选择配置运行的,不是matlab那样直接运行文件

4.编辑realtime_comm部分

(1)函数文件

把原项目的函数代码粘贴进realtime_comm文件夹,realtime_comm_main.cpp函数和CMakeLists.txt如附录所示,不多赘述

(2)运行结果

核心代码

file_comm部分

file_comm_main.cpp

cpp 复制代码
#include <iostream>
#include <vector>
#include <complex>
#include <thread>
#include <atomic>
#include <chrono>
#include <fstream>

#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/stream.hpp>

using namespace std;

static atomic<bool> stop_signal_called(false);

// ======== 发送线程函数 ========
void trans(
    vector<complex<double>>        buff,
    uhd::tx_streamer::sptr         tx_stream,
    uhd::tx_metadata_t             md,
    size_t                         total_num_samps,
    double                         timeout,
    vector<uhd::time_spec_t>       frame_start_times)
{
    int tx_num = (int)frame_start_times.size();
    for(int i = 0; i < tx_num; i++)
    {
        if(stop_signal_called) break;
        md.has_time_spec  = true;
        md.time_spec      = frame_start_times[i];
        md.start_of_burst = false;
        md.end_of_burst   = false;

        size_t num_acc_samps = 0;
        while(num_acc_samps < total_num_samps)
        {
            size_t samps_to_send = min(total_num_samps - num_acc_samps, buff.size());
            size_t num_tx_samps  = tx_stream->send(&buff.front(), samps_to_send, md, timeout);
            md.has_time_spec     = false;
            num_acc_samps       += num_tx_samps;
        }
        md.end_of_burst = true;
        tx_stream->send("", 0, md);
        cout << "[DBG] 第 " << i << " 帧发送完,样本数: " << total_num_samps << endl;
    }
}

// ======== 接收函数 ========
void recv(
    uhd::usrp::multi_usrp::sptr    usrp,
    size_t                         rx_total_num_samps,
    vector<uhd::time_spec_t>       frame_start_times,
    ofstream&                      rx_log)
{
    uhd::stream_args_t stream_args("fc64", "");
    stream_args.channels = {0};
    uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args);

    uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
    stream_cmd.num_samps  = rx_total_num_samps;
    stream_cmd.stream_now = false;

    uhd::rx_metadata_t md;
    vector<complex<double>> buff(rx_stream->get_max_num_samps());
    vector<void*> buffs;
    buffs.push_back(&buff.front());

    int num = (int)frame_start_times.size();

    for(int i = 0; i < num; i++)
    {
        if(stop_signal_called) break;

        stream_cmd.time_spec = frame_start_times[i];
        rx_stream->issue_stream_cmd(stream_cmd);

        size_t num_acc_samps = 0;
        vector<complex<double>> all_samples;

        while(!stop_signal_called && num_acc_samps < rx_total_num_samps)
        {
            size_t num_rx_samps = rx_stream->recv(buffs, buff.size(), md, 1.0, true);
            if(stop_signal_called) break;

            if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT)
            {
                cout << "Timeout while streaming\n";
                break;
            }
            if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW)
                continue;
            if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND)
            {
                cerr << "[WARN] Late command, skipping frame " << i << "\n";
                all_samples.clear();
                num_acc_samps = rx_total_num_samps;
                break;
            }
            if(md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE)
            {
                cerr << "Receiver error: " << md.strerror() << "\n";
                break;
            }

            all_samples.insert(all_samples.end(),
                               buff.begin(), buff.begin() + num_rx_samps);
            num_acc_samps += num_rx_samps;
        }

        if(all_samples.empty()) continue;

        cout << "[DBG] 收到样本数: " << all_samples.size() << endl;

        // 写帧长(4字节),再写IQ数据(float32 interleaved)
        uint32_t frame_len = (uint32_t)all_samples.size();
        rx_log.write((char*)&frame_len, sizeof(uint32_t));
        for(auto& c : all_samples)
        {
            float re = (float)c.real();
            float im = (float)c.imag();
            rx_log.write((char*)&re, sizeof(float));
            rx_log.write((char*)&im, sizeof(float));
        }
        cout << "[DBG] 已写入帧,样本数: " << frame_len << endl;
    }

    // 结束RX流
    uhd::stream_cmd_t stop_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
    stop_cmd.stream_now = true;
    rx_stream->issue_stream_cmd(stop_cmd);

    vector<complex<double>> drain_buff(rx_stream->get_max_num_samps());
    vector<void*> drain_buffs(1, &drain_buff.front());
    uhd::rx_metadata_t drain_md;
    while(true)
    {
        size_t nsamps = rx_stream->recv(drain_buffs, drain_buff.size(), drain_md, 0.1, false);
        if(nsamps == 0) break;
    }
    this_thread::sleep_for(chrono::milliseconds(50));
}

// ======== main ========
int main()
{
    // ---------- 参数 ----------
    double freq               = 3.51e9;
    double rate               = 100e6;
    double tx_gain            = 20.0;
    double rx_gain            = 20.0;
    string device_addr        = "192.168.40.2";
    double seconds_in_future  = 0.05;
    const int frames_per_batch = 10;
    const double frame_period  = 0.5;
    int run_nums               = 200;   // 总帧数

    // ---------- 初始化USRP ----------
    cout << "初始化USRP..." << endl;
    auto usrp = uhd::usrp::multi_usrp::make(
        uhd::device_addr_t("type=x300,master_clock_rate=200e6,addr=" + device_addr)
    );

    usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t("B:0"));
    usrp->set_rx_subdev_spec(uhd::usrp::subdev_spec_t("A:0"));
    usrp->set_tx_rate(rate);
    usrp->set_rx_rate(rate);
    usrp->set_tx_freq(uhd::tune_request_t(freq));
    usrp->set_rx_freq(uhd::tune_request_t(freq));
    usrp->set_tx_gain(tx_gain);
    usrp->set_rx_gain(rx_gain);

    cout << "TX率: " << usrp->get_tx_rate()/1e6 << " Msps" << endl;
    cout << "RX率: " << usrp->get_rx_rate()/1e6 << " Msps" << endl;

    this_thread::sleep_for(chrono::milliseconds(200));

    // ---------- 读取MATLAB生成的发射波形 ----------
    ifstream wf(string(PROJECT_DATA_DIR) + "/tx_waveform.dat", ios::binary);
    if(!wf) { cerr << "无法打开tx_waveform.dat" << endl; return 1; }

    wf.seekg(0, ios::end);
    size_t file_bytes = wf.tellg();
    wf.seekg(0, ios::beg);

    size_t num_samples = file_bytes / (2 * sizeof(float));
    vector<float> raw(num_samples * 2);
    wf.read(reinterpret_cast<char*>(raw.data()), file_bytes);
    wf.close();

    vector<complex<double>> tx_samples(num_samples);
    for(size_t i = 0; i < num_samples; i++)
        tx_samples[i] = complex<double>(raw[2*i], raw[2*i+1]);

    cout << "读取波形: " << num_samples << " 个样本" << endl;

    size_t tx_total_num_samps = tx_samples.size();
    size_t rx_total_num_samps = (size_t)(tx_samples.size() * 1.5);

    // ---------- 构造TX streamer ----------
    uhd::stream_args_t tx_args("fc64", "");
    tx_args.channels = {0};
    uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(tx_args);

    // ---------- 打开接收数据文件(全程只开一次)----------
    ofstream rx_log(string(PROJECT_DATA_DIR) + "/rx_result.dat", ios::binary);
    if(!rx_log) { cerr << "无法创建rx_all.dat" << endl; return 1; }

    // ---------- 主循环 ----------
    cout << "开始收发..." << endl;

    int batch_idx    = 0;
    int total_frames = 0;

    while(!stop_signal_called && total_frames < run_nums)
    {
        usrp->set_time_now(uhd::time_spec_t(0.0));

        vector<uhd::time_spec_t> frame_start_times(frames_per_batch);
        for(int i = 0; i < frames_per_batch; i++)
            frame_start_times[i] = uhd::time_spec_t(
                seconds_in_future + frame_period * static_cast<double>(i));

        uhd::tx_metadata_t tx_md;
        thread tx_thread([&](){
            trans(tx_samples, tx_stream, tx_md,
                  tx_total_num_samps, 0.5, frame_start_times);
        });

        recv(usrp, rx_total_num_samps, frame_start_times, rx_log);

        tx_thread.join();

        batch_idx++;
        total_frames += frames_per_batch;
        cout << "完成第 " << batch_idx << " 批,共 " << total_frames << " 帧" << endl;
    }

    rx_log.close();
    cout << "结束,数据已保存至 rx_all.dat" << endl;
    return 0;
}

file_comm中的CMakeLists.txt

cpp 复制代码
add_executable(file_comm file_comm_main.cpp)
target_compile_definitions(file_comm PRIVATE
        PROJECT_DATA_DIR="${CMAKE_SOURCE_DIR}/data"
)
target_include_directories(file_comm PRIVATE
        ${CMAKE_SOURCE_DIR}/common
)
target_link_libraries(file_comm PRIVATE
        ${UHD_LIBRARIES}
        Boost::filesystem Boost::system Boost::thread
        ${FFTW3_LIBRARIES}
        Threads::Threads
)

realtime_comm部分

realtime_comm_main.cpp

cpp 复制代码
#include <iostream>
#include <vector>
#include <complex>
#include <thread>
#include <atomic>
#include <chrono>
#include <cstring>

#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/stream.hpp>

#include "receiver_chain.h"
#include "frame_builder.h"


using namespace std;
using namespace fun;

static atomic<bool> stop_signal_called(false);

// ======== 回调函数 ========
void callback(vector<vector<unsigned char>> payloads)
{
    for(auto& p : payloads)
        cout << "Received: " << string(p.begin(), p.end()) << endl;
}

// ======== 发送线程函数(完全照搬感知代码trans) ========
void trans(
    vector<complex<double>>        buff,
    uhd::tx_streamer::sptr         tx_stream,
    uhd::tx_metadata_t             md,
    size_t                         total_num_samps,
    double                         timeout,
    vector<uhd::time_spec_t>       frame_start_times)
{
    int tx_num = (int)frame_start_times.size();
    for(int i = 0; i < tx_num; i++)
    {
        if(stop_signal_called) break;
        md.has_time_spec  = true;
        md.time_spec      = frame_start_times[i];
        md.start_of_burst = false;
        md.end_of_burst   = false;

        size_t num_acc_samps = 0;
        while(num_acc_samps < total_num_samps)
        {
            size_t samps_to_send = min(total_num_samps - num_acc_samps, buff.size());
            size_t num_tx_samps  = tx_stream->send(&buff.front(), samps_to_send, md, timeout);
            md.has_time_spec     = false;
            num_acc_samps       += num_tx_samps;
        }
        md.end_of_burst = true;
        tx_stream->send("", 0, md);
        // 每帧发送完后
        cout << "[DBG] 第 " << i << " 帧发送完,样本数: " << total_num_samps << endl;
    }
}

// ======== 接收函数(完全照搬感知代码recv,去掉感知计算部分) ========
void recv(
    uhd::usrp::multi_usrp::sptr       usrp,
    size_t                             rx_total_num_samps,
    vector<uhd::time_spec_t>           frame_start_times,
    receiver_chain&                    rec_chain,
    void(*cb)(vector<vector<unsigned char>>))
{
    uhd::stream_args_t stream_args("fc64", "");
    stream_args.channels = {0};
    uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args);

    uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
    stream_cmd.num_samps  = rx_total_num_samps;
    stream_cmd.stream_now = false;

    uhd::rx_metadata_t md;
    vector<complex<double>> buff(rx_stream->get_max_num_samps());
    vector<void*> buffs;
    buffs.push_back(&buff.front());

    int num = (int)frame_start_times.size();

    for(int i = 0; i < num; i++)
    {
        if(stop_signal_called) break;

        stream_cmd.time_spec = frame_start_times[i];
        rx_stream->issue_stream_cmd(stream_cmd);

        size_t num_acc_samps = 0;
        vector<complex<double>> all_samples;

        while(!stop_signal_called && num_acc_samps < rx_total_num_samps)
        {
            size_t num_rx_samps = rx_stream->recv(buffs, buff.size(), md, 1.0, true);
            if(stop_signal_called) break;

            if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT)
            {
                cout << "Timeout while streaming\n";
                break;
            }
            if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW)
                continue;
            if(md.error_code == uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND)
            {
                cerr << "[WARN] Late command, skipping frame " << i << "\n";
                all_samples.clear();
                num_acc_samps = rx_total_num_samps;
                break;
            }
            if(md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE)
            {
                cerr << "Receiver error: " << md.strerror() << "\n";
                break;
            }

            all_samples.insert(all_samples.end(),
                               buff.begin(), buff.begin() + num_rx_samps);
            num_acc_samps += num_rx_samps;
        }

        if(all_samples.empty()) continue;

        // 加这行,看收到了多少样本
        cout << "[DBG] 收到样本数: " << all_samples.size() << endl;

        auto packets = rec_chain.process_samples(all_samples);

        // 加这行,看解出了多少包
        cout << "[DBG] 解出包数: " << packets.size() << endl;

        cb(packets);

        // 送入接收链路处理
        //auto packets = rec_chain.process_samples(all_samples);
        //cb(packets);
    }

    // 优雅结束RX流
    uhd::stream_cmd_t stop_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
    stop_cmd.stream_now = true;
    rx_stream->issue_stream_cmd(stop_cmd);

    vector<complex<double>> drain_buff(rx_stream->get_max_num_samps());
    vector<void*> drain_buffs(1, &drain_buff.front());
    uhd::rx_metadata_t drain_md;
    while(true)
    {
        size_t nsamps = rx_stream->recv(drain_buffs, drain_buff.size(), drain_md, 0.1, false);
        if(nsamps == 0) break;
    }
    this_thread::sleep_for(chrono::milliseconds(50));
}

// ======== main ========
int main()
{
    // ---------- 参数 ----------
    double freq               = 3.51e9;
    double rate               = 50e6;
    double tx_gain            = 20.0;
    double rx_gain            = 20.0;
    string device_addr        = "192.168.40.2";
    double seconds_in_future  = 0.05;   // 每帧提前量
    const int frames_per_batch = 10;    // 每批帧数
    const double frame_period  = 0.5;   // 帧间隔(s)

    // ---------- 初始化USRP ----------
    cout << "初始化USRP..." << endl;
    auto usrp = uhd::usrp::multi_usrp::make(
        uhd::device_addr_t("type=x300,master_clock_rate=200e6,addr=" + device_addr)
    );

    usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t("B:0"));
    usrp->set_rx_subdev_spec(uhd::usrp::subdev_spec_t("A:0"));
    usrp->set_tx_rate(rate);
    usrp->set_rx_rate(rate);
    usrp->set_tx_freq(uhd::tune_request_t(freq));
    usrp->set_rx_freq(uhd::tune_request_t(freq));
    usrp->set_tx_gain(tx_gain);
    usrp->set_rx_gain(rx_gain);

    cout << "TX率: " << usrp->get_tx_rate()/1e6 << " Msps" << endl;
    cout << "RX率: " << usrp->get_rx_rate()/1e6 << " Msps" << endl;

    this_thread::sleep_for(chrono::milliseconds(200));

    // ---------- 构造TX streamer(只创建一次,不会和RX同时存在) ----------
    uhd::stream_args_t tx_args("fc64", "");
    tx_args.channels = {0};
    uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(tx_args);

    // ---------- 构造发送帧 ----------
    frame_builder fb;
    string s = "Hello World";
    vector<unsigned char> data(s.begin(), s.end());
    auto tx_samples = fb.build_frame(data, RATE_1_2_BPSK);

    size_t tx_total_num_samps = tx_samples.size();
    size_t rx_total_num_samps = (size_t)(tx_samples.size() * 1.5); // 多收一点保证帧完整

    receiver_chain rec_chain;

    // ---------- 主循环(完全照搬感知代码批次结构) ----------
    cout << "开始收发..." << endl;

    int batch_idx   = 0;
    int total_frames = 0;
    int run_nums    = 200; // 总帧数

    while(!stop_signal_called && total_frames < run_nums)
    {
        // 重置硬件时钟(与感知代码完全一致)
        usrp->set_time_now(uhd::time_spec_t(0.0));

        // 构造这一批的帧时间表
        vector<uhd::time_spec_t> frame_start_times(frames_per_batch);
        for(int i = 0; i < frames_per_batch; i++)
            frame_start_times[i] = uhd::time_spec_t(
                seconds_in_future + frame_period * static_cast<double>(i));

        // 启动发射线程
        uhd::tx_metadata_t tx_md;
        thread tx_thread([&](){
            trans(tx_samples, tx_stream, tx_md,
                  tx_total_num_samps, 0.5, frame_start_times);
        });

        // 主线程做接收(RX streamer在recv内部创建和销毁)
        recv(usrp, rx_total_num_samps, frame_start_times,
             rec_chain, callback);

        tx_thread.join();

        batch_idx++;
        total_frames += frames_per_batch;
        cout << "完成第 " << batch_idx << " 批,共 " << total_frames << " 帧" << endl;
    }

    cout << "结束" << endl;
    return 0;
}

realtime_comm中的CMakeLists.txt

cpp 复制代码
add_executable(realtime_comm
    realtime_comm_main.cpp
    channel_est.cpp
    fft.cpp
    fft_symbols.cpp
    frame_builder.cpp
    frame_decoder.cpp
    frame_detector.cpp
    interleaver.cpp
    modulator.cpp
    parity.cpp
    phase_tracker.cpp
    ppdu.cpp
    puncturer.cpp
    receiver_chain.cpp
    symbol_mapper.cpp
    timing_sync.cpp
    viterbi.cpp
)

target_include_directories(realtime_comm PRIVATE
    ${CMAKE_SOURCE_DIR}/common
)

target_link_libraries(realtime_comm PRIVATE
    ${UHD_LIBRARIES}
    Boost::filesystem Boost::system Boost::thread
    ${FFTW3_LIBRARIES}
    Threads::Threads
)
target_include_directories(realtime_comm PRIVATE
        ${CMAKE_SOURCE_DIR}/common
)
target_link_libraries(realtime_comm PRIVATE
        ${UHD_LIBRARIES}
        Boost::filesystem Boost::system Boost::thread
        ${FFTW3_LIBRARIES}
        Threads::Threads
)

过程中遇到的问题

相关推荐
郝学胜_神的一滴2 小时前
CMake 021: IF 条件判据详诠
c++·cmake
猪脚踏浪2 小时前
linux 拷贝文件或目录到指定的位置
linux
_wyt00116 小时前
洛谷 B3930 [GESP202312 五级] 烹饪问题 题解
c++·gesp
大树8818 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠18 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质18 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush418 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行52019 小时前
Linux 11 动态监控指令top
linux
Inhand陈工19 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
玖玥拾20 小时前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器