1 概要

博主最近因为工程需求,需要在两个嵌入式设备之间传输图片,具体功能如下描述:

硬件资源:
①米联客安路F3P-CZ02-FPSoc(FPGA) (HOST端)
②rk3568 (Client端)

满足功能:
①rk3568可以将消息包通过Socket接口发送给FPGA
②FPGA通过解包消息报,读取图片数据,并存入相应的文件夹中

2 代码文件结构在这里插入图片描述

整个工程代码分为三个结构,Client,Host,Common,其中:

  1. Client文件夹内存放的为与Client端相关的函数
  2. Host文件夹内存放的为与Host端相关的函数
  3. Common文件夹内存放的为公共配置函数及部分工具包

2.1 Client文件夹下函数介绍

ClientClient文件夹下有4个文件:
在这里插入图片描述

  1. client_main.cpp:
    用于快速测试的文件demo。
  2. client_main_test.cpp:
    根据用户需求进行功能设计的文件,其中对于封装类别的使用参考了client_main.cpp中的函数。
  3. ImageClient.h:
    ImageClient类别的头文件。
  4. ImageClient.cpp:
    ImageClient类别的源文件。

2.1.1 ImageClient.h

代码如下:

#pragma once
#include <cstdint>
#include <string>

class ImageClient
{
public:
    ImageClient(std::string server_ip, uint16_t port);
    // image_name 可留空,默认取 image_path 的文件名
    bool sendImage(const std::string &mode,
                   const std::string &image_path,
                   const std::string &image_name_opt = std::string());

private:
    std::string server_ip_;
    uint16_t port_;
};

其中封装了两个函数,一个是初始化时会自动调用的ImageClient函数,一个是发送数据使用的sendImage函数,其中需要传递参数为检测模式mode, 发送图片的图片路径image_path, 图片的名字image_name_opt。

私有变量为:
远端的ip:server_ip_,远端的监听端口:port_。

2.1.2 ImageClient.cpp

整体上是在实现一个图片上传客户端
给它一个服务器 IP、端口号和本地图片路径,它会:

  1. 把图片文件读到内存里
  2. 用自定义协议组装一个消息头(MsgHeader),包含 magic、版本号、mode 字符串长度、图片名长度、文件大小等;
  3. 建立 TCP 连接(IPv4)到指定服务器;
  4. 依次发送:消息头 → mode 文本 → 文件名 → 图片二进制数据。
    代码如下:
#include "ImageClient.h"
#include "ImageTransferCommon.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#include <filesystem>
#include <fstream>
#include <iostream>
#include <vector>

namespace fs = std::filesystem;
using namespace itx;

/**
 * @brief 构造函数
 * 
 * 保存目标服务器的 IP 和端口,用于后续建立 TCP 连接进行图片传输。
 *
 * @param server_ip 服务器 IPv4 地址字符串,例如 "127.0.0.1"
 * @param port      服务器监听端口号
 */
ImageClient::ImageClient(std::string server_ip, uint16_t port)
    : server_ip_(std::move(server_ip)), port_(port) {}

/**
 * @brief 发送一张图片到服务器
 *
 * 1. 从本地读取指定路径的图片文件到内存;
 * 2. 根据自定义协议填充消息头 MsgHeader(magic、版本号、mode 长度、文件名长度、文件大小等);
 * 3. 建立 TCP 连接到构造函数中指定的 server_ip_ 和 port_;
 * 4. 依次发送:消息头 → mode 字符串 → 图片名 → 图片二进制数据;
 * 5. 发送完成后关闭 socket。
 *
 * @param mode           业务模式字符串(例如 "upload"、"classify" 等),由服务器按约定解析
 * @param image_path     本地图片文件路径
 * @param image_name_opt 可选的图片名;如果为空,则使用 image_path 中的文件名部分
 * @return true          发送成功
 * @return false         发送失败(过程中会在标准错误输出错误信息)
 */
bool ImageClient::sendImage(const std::string &mode,
                            const std::string &image_path,
                            const std::string &image_name_opt)
{
    // 1. 打开并读取图片文件到内存缓冲区
    std::ifstream ifs(image_path, std::ios::binary);
    if (!ifs.is_open())
    {
        std::cerr << "Open file failed: " << image_path << "\n";
        return false;
    }
    ifs.seekg(0, std::ios::end);
    std::streamoff sz = ifs.tellg();
    ifs.seekg(0, std::ios::beg);
    if (sz < 0)
    {
        std::cerr << "Tell size failed: " << image_path << "\n";
        return false;
    }
    std::vector<char> filebuf(static_cast<size_t>(sz));
    ifs.read(filebuf.data(), sz);

    // 2. 确定要发送的文件名(优先使用外部指定的 image_name_opt)
    std::string name = image_name_opt.empty()
                           ? fs::path(image_path).filename().string()
                           : image_name_opt;

    // mode 和 name 限制在 uint16_t 能表示的范围内(协议字段是 16 位长度)
    if (mode.size() > 65535 || name.size() > 65535)
    {
        std::cerr << "mode/name too long.\n";
        return false;
    }

    // 3. 创建 TCP socket
    int fd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket");
        return false;
    }

    // 4. 填写服务器地址结构体
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port_);
    if (::inet_pton(AF_INET, server_ip_.c_str(), &addr.sin_addr) != 1)
    {
        std::cerr << "Invalid server IP: " << server_ip_ << "\n";
        ::close(fd);
        return false;
    }

    // 5. 连接到服务器
    if (::connect(fd, (sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("connect");
        ::close(fd);
        return false;
    }

    // 6. 构造并发送消息头 + 文本字段 + 文件数据
    MsgHeader h{};
    std::memcpy(h.magic, MAGIC, 4);                           // 魔数标识协议
    h.version  = htons(VERSION);                              // 协议版本
    h.mode_len = htons(static_cast<uint16_t>(mode.size()));   // mode 字符串长度
    h.name_len = htons(static_cast<uint16_t>(name.size()));   // 文件名长度
    h.file_size = htonll(static_cast<uint64_t>(filebuf.size())); // 文件大小(字节数)

    // 依次发送:头 → mode → name → 文件数据
    if (!send_all(fd, &h, sizeof(h)) ||
        !send_all(fd, mode.data(), mode.size()) ||
        !send_all(fd, name.data(), name.size()) ||
        !send_all(fd, filebuf.data(), filebuf.size()))
    {
        std::cerr << "send failed.\n";
        ::close(fd);
        return false;
    }

    // 7. 关闭连接,打印发送结果
    ::close(fd);
    std::cout << "Sent: mode=" << mode
              << ", name=" << name
              << ", bytes=" << filebuf.size() << "\n";
    return true;
}

2.1.3 client_main.cpp

测试文件demo:

#include "ImageClient.h"
#include <iostream>
#include <string>
#include "Config.h"

/*
使用方式:
./client 192.168.1.10 5000 mode2_dataset1 /home/user/pics/dog.png dog-renamed.png
*/

#define CONF_PATH "../Common/lan_image_transfer.conf"

int main(int argc, char *argv[])
{
    if (argc < 5 || argc > 6)
    {
        std::cerr << "Usage: client <server_ip> <port> <mode> <image_path> [image_name]\n";
        return 1;
    }
    std::string ip = argv[1];
    uint16_t port = static_cast<uint16_t>(std::stoi(argv[2]));
    std::string mode = argv[3];
    std::string path = argv[4];
    std::string name = (argc == 6) ? argv[5] : std::string();

    AppConfig cfg;
    std::string err;
    if (!loadConfig(CONF_PATH, cfg, &err))
    {
        std::cerr << "Load config failed: " << err << "\n";
        return 1;
    }

    ImageClient cli(cfg.host_ip, cfg.port);
    bool ok = cli.sendImage(mode, path, name);
    return ok ? 0 : 2;
}

2.1.4 client_main_test.cpp(博主自己按功能实现的文件)

RK3568接收到8种模式中的其中一个命令后将指定文件夹中的图片传输到远程的FPGA(Host)中。

#include "ImageClient.h"
#include "Config.h"
#include <iostream>
#include <string>
#include <filesystem>
#include <vector>
#include <algorithm>
#include <cctype>

#define CONF_PATH "../../Common/lan_image_transfer.conf"

/*

发送的数据格式内容为:模式,图片数据,图片名字

*/

namespace fs = std::filesystem;

// 允许的 8 种模式
static bool isAllowedMode(const std::string &m)
{
    static const std::vector<std::string> modes = {
        "mode1_picture1", "mode1_picture2", "mode1_picture3", "mode1_picture4",
        "mode2_dataset1", "mode2_dataset2", "mode3_dataset1", "mode3_dataset2"};
    return std::find(modes.begin(), modes.end(), m) != modes.end();
}

// 简单判断是否图片文件
static bool isImageFile(const fs::path &p)
{
    if (!fs::is_regular_file(p))
        return false;
    std::string ext = p.extension().string();
    std::transform(ext.begin(), ext.end(), ext.begin(),
                   [](unsigned char c)
                   { return (char)std::tolower(c); });
    static const std::vector<std::string> exts = {
        ".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff", ".gif", ".webp"};
    return std::find(exts.begin(), exts.end(), ext) != exts.end();
}

// 去掉前后空白
static std::string trim(std::string s)
{
    auto issp = [](unsigned char c)
    { return std::isspace(c); };
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [&](unsigned char c)
                                    { return !issp(c); }));
    s.erase(std::find_if(s.rbegin(), s.rend(), [&](unsigned char c)
                         { return !issp(c); })
                .base(),
            s.end());
    return s;
}

int main(int argc, char *argv[])
{
    // 读取配置
    AppConfig cfg;
    std::string err;
    if (!loadConfig(CONF_PATH, cfg, &err))
    {
        std::cerr << "Load config failed: " << err << "\n";
        return 1;
    }
    if (cfg.host_ip.empty() || cfg.port == 0)
    {
        std::cerr << "Config missing 'host_ip' or 'port'\n";
        return 1;
    }

    ImageClient cli(cfg.host_ip, cfg.port);
    std::cout << "目标主机: " << cfg.host_ip << ":" << cfg.port << "\n";
    std::cout << "输入 'list' 查看可用模式,输入 'quit' 或 'exit' 退出。\n";

    while (true)
    {
        // 读模式
        std::string mode;
        std::cout << "\n请输入模式: ";
        if (!std::getline(std::cin, mode))
            break;
        mode = trim(mode);
        if (mode == "quit" || mode == "exit")
            break;
        if (mode == "list")
        {
            std::cout << "可用模式: "
                         "\n 1.mode1_picture1 \n 2.mode1_picture2 \n 3.mode1_picture3 \n 4.mode1_picture4 \n"
                         " 5.mode2_dataset1 \n 6.mode2_dataset2 \n 7.mode3_dataset1 \n 8.mode3_dataset2 \n";
            continue;
        }
        if (!isAllowedMode(mode))
        {
            std::cerr << "模式无效,请重试(输入 'list' 查看可用模式)。\n";
            continue;
        }

        // 读文件夹
        std::string folder;
        std::cout << "请输入图片文件夹路径: ";
        if (!std::getline(std::cin, folder))
            break;
        folder = trim(folder);
        fs::path dir(folder);
        if (!fs::exists(dir) || !fs::is_directory(dir))
        {
            std::cerr << "目录不存在或不是目录: " << dir << "\n";
            continue;
        }

        // 收集图片
        std::vector<fs::path> files;
        for (const auto &entry : fs::directory_iterator(dir))
        {
            if (isImageFile(entry.path()))
                files.push_back(entry.path());
        }
        std::sort(files.begin(), files.end());
        if (files.empty())
        {
            std::cout << "该目录未发现图片文件。\n";
            continue;
        }

        std::cout << "发现 " << files.size() << " 个图片文件,开始发送...\n";
        size_t ok_cnt = 0, fail_cnt = 0;

        for (const auto &f : files)
        {
            const std::string path = f.string();
            const std::string name = f.filename().string(); // 用文件名作为远端保存名
            const bool ok = cli.sendImage(mode, path, name);
            if (ok)
                ++ok_cnt;
            else
                ++fail_cnt;
        }

        std::cout << "发送完成:成功 " << ok_cnt << " 个,失败 " << fail_cnt << " 个。\n";
        // 回到循环,可继续输入下一个模式与文件夹
    }

    std::cout << "已退出。\n";
    return 0;
}

3 总结

本章节介绍了嵌入式设备间Socket通信传输图片 Host端函数。

4 其余章节

【Socket消息传递】(1) 嵌入式设备间Socket通信传输图片
【Socket消息传递详细版本】(2) 嵌入式设备间Socket通信传输图片 Common公共函数
【Socket消息传递详细版本】(3) 嵌入式设备间Socket通信传输图片 Client端函数
【Socket消息传递详细版本】(4) 嵌入式设备间Socket通信传输图片 Host端函数

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐