文章目录
- [1 概要](#1 概要)
- [2 代码文件结构](#2 代码文件结构
) -
- [2.1 Client文件夹下函数介绍](#2.1 Client文件夹下函数介绍)
-
- [2.1.1 ImageClient.h](#2.1.1 ImageClient.h)
- [2.1.2 ImageClient.cpp](#2.1.2 ImageClient.cpp)
- [2.1.3 client_main.cpp](#2.1.3 client_main.cpp)
- [2.1.4 client_main_test.cpp(博主自己按功能实现的文件)](#2.1.4 client_main_test.cpp(博主自己按功能实现的文件))
- [3 总结](#3 总结)
- [4 其余章节](#4 其余章节)
1 概要
博主最近因为工程需求,需要在两个嵌入式设备之间传输图片,具体功能如下描述:
硬件资源:
①米联客安路F3P-CZ02-FPSoc(FPGA) (HOST端)
②rk3568 (Client端)
满足功能:
①rk3568可以将消息包通过Socket接口发送给FPGA
②FPGA通过解包消息报,读取图片数据,并存入相应的文件夹中
2 代码文件结构
整个工程代码分为三个结构,Client,Host,Common,其中:
- Client文件夹内存放的为与Client端相关的函数
- Host文件夹内存放的为与Host端相关的函数
- Common文件夹内存放的为公共配置函数及部分工具包
2.1 Client文件夹下函数介绍
ClientClient文件夹下有4个文件:

- client_main.cpp:
用于快速测试的文件demo。 - client_main_test.cpp:
根据用户需求进行功能设计的文件,其中对于封装类别的使用参考了client_main.cpp中的函数。 - ImageClient.h:
ImageClient类别的头文件。 - ImageClient.cpp:
ImageClient类别的源文件。
2.1.1 ImageClient.h
代码如下:
c
#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、端口号和本地图片路径,它会:
- 把图片文件读到内存里
- 用自定义协议组装一个消息头(MsgHeader),包含 magic、版本号、mode 字符串长度、图片名长度、文件大小等;
- 建立 TCP 连接(IPv4)到指定服务器;
- 依次发送:消息头 → mode 文本 → 文件名 → 图片二进制数据。
代码如下:
c
#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:
c
#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)中。
c
#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端函数