当前位置: 首页 > news >正文

嵌入式Linux:移植使用scp指令

文章目录

前言

一、mini_scp?

二、实现原理

三、命令协议

四、代码指令实现

总结

优势

劣势


前言

在嵌入式linux开发中经常需要文件传输,我现在使用NFS来实现ubuntu和嵌入式Linux开发板的文件共享传输,但是还是想要使用scp指令,快捷一点,就想着移植openssh,下载源码解压编译后,在开发板上生成密钥报错,如下:

搜索发现,重新移植根文件系统来升级GLIC,哇,就很离谱啊,,我就是想使用个scp指令而已,搞这么麻烦干嘛,你干嘛,哎呦!!!!!!!,,,,,,但是无所谓,不更新根文件系统,我直接手动创建一个mini_scp,在开发板和主机之间轻松传输文件。


一、mini_scp?

仿照scp的实现,静态链接,不依赖开发板上的glibc版本,轻量级,资源占用小,使用与标准SCP相似的语法,便于使用,支持文件上传和下载。

二、实现原理

mini_scp工具基于TCP套接字通信,采用客户端-服务器架构:

  1. 服务器端运行在开发板上,监听指定端口,我是用的是1234端口
  2. 客户端运行在开发机上,ubuntu连接到开发板,可设置开发板开机自启动并在后台运行
  3. 通过简单的命令协议实现文件传输,类似scp执行

三、命令协议

  1. PUT <路径>|<文件名> - 上传文件到开发板
  2. GET <路径> - 从开发板下载文件
  3. OK - 命令确认
  4. ERROR - 错误响应

四、代码指令实现

# 修改mini_scp.c代码
cd ~/linux/IMX6ULL/tool/# 创建更新版本的mini_scp
cat > mini_scp_v2.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <libgen.h>
#include <errno.h>#define PORT 1234
#define BUFFER_SIZE 4096// 用于在开发板上创建目录的函数
int mkdirp(const char *dir) {char tmp[1024];char *p = NULL;size_t len;snprintf(tmp, sizeof(tmp), "%s", dir);len = strlen(tmp);if (tmp[len - 1] == '/')tmp[len - 1] = 0;for (p = tmp + 1; *p; p++) {if (*p == '/') {*p = 0;mkdir(tmp, 0755);*p = '/';}}return mkdir(tmp, 0755);
}// 服务器端函数 - 在开发板上运行
void scp_server() {int server_fd, client_fd;struct sockaddr_in address;socklen_t addrlen = sizeof(address);char buffer[BUFFER_SIZE];char filepath[BUFFER_SIZE];char dirpath[BUFFER_SIZE];int bytes_read;// 创建socketserver_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd < 0) {perror("socket创建失败");exit(EXIT_FAILURE);}// 设置socket选项int opt = 1;setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 配置地址memset(&address, 0, sizeof(address));address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("绑定失败");exit(EXIT_FAILURE);}// 监听if (listen(server_fd, 5) < 0) {perror("监听失败");exit(EXIT_FAILURE);}printf("SCP服务已启动,监听端口 %d...\n", PORT);while (1) {// 接受连接client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen);if (client_fd < 0) {perror("接受连接失败");continue;}printf("新连接已建立\n");// 接收命令memset(buffer, 0, BUFFER_SIZE);bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);if (bytes_read <= 0) {close(client_fd);continue;}// 解析命令if (strncmp(buffer, "PUT ", 4) == 0) {// PUT命令: 接收文件char *path_info = buffer + 4;char *filename_part = strchr(path_info, '|');if (filename_part) {// 分离路径和文件名*filename_part = '\0';filename_part++;// 构建完整路径if (path_info[strlen(path_info) - 1] == '/') {sprintf(filepath, "%s%s", path_info, filename_part);} else {sprintf(filepath, "%s/%s", path_info, filename_part);}} else {strcpy(filepath, path_info);}printf("接收文件: %s\n", filepath);// 创建目录strcpy(dirpath, filepath);char *dir = dirname(dirpath);if (strcmp(dir, ".") != 0) {mkdirp(dir);}// 打开文件int file_fd = open(filepath, O_WRONLY | O_CREAT | O_TRUNC, 0644);if (file_fd < 0) {perror("无法创建文件");write(client_fd, "ERROR", 5);close(client_fd);continue;}// 发送OK确认write(client_fd, "OK", 2);// 接收文件内容while ((bytes_read = read(client_fd, buffer, BUFFER_SIZE)) > 0) {write(file_fd, buffer, bytes_read);}close(file_fd);printf("文件接收完成: %s\n", filepath);} else if (strncmp(buffer, "GET ", 4) == 0) {// GET命令: 发送文件strcpy(filepath, buffer + 4);printf("发送文件: %s\n", filepath);// 打开文件int file_fd = open(filepath, O_RDONLY);if (file_fd < 0) {perror("无法打开文件");write(client_fd, "ERROR", 5);close(client_fd);continue;}// 发送OK确认write(client_fd, "OK", 2);// 发送文件内容while ((bytes_read = read(file_fd, buffer, BUFFER_SIZE)) > 0) {write(client_fd, buffer, bytes_read);}close(file_fd);printf("文件发送完成: %s\n", filepath);} else {write(client_fd, "ERROR", 5);}close(client_fd);}close(server_fd);
}// 客户端函数 - 在Ubuntu上运行
void scp_client(int mode, const char *src_path, const char *dst_path, const char *host) {int sock_fd, file_fd;struct sockaddr_in address;char buffer[BUFFER_SIZE];int bytes_read;char command[BUFFER_SIZE * 2];// 创建socketsock_fd = socket(AF_INET, SOCK_STREAM, 0);if (sock_fd < 0) {perror("socket创建失败");exit(EXIT_FAILURE);}// 配置地址memset(&address, 0, sizeof(address));address.sin_family = AF_INET;address.sin_port = htons(PORT);if (inet_pton(AF_INET, host, &address.sin_addr) <= 0) {perror("无效的地址");exit(EXIT_FAILURE);}// 连接printf("连接到 %s:%d...\n", host, PORT);if (connect(sock_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("连接失败");exit(EXIT_FAILURE);}if (mode == 0) {  // PUT - 上传文件到开发板// 打开文件file_fd = open(src_path, O_RDONLY);if (file_fd < 0) {perror("无法打开源文件");exit(EXIT_FAILURE);}// 获取源文件名char *src_filename = basename((char *)src_path);// 检查目标路径是否以/结尾,表示目录int is_dir = (dst_path[strlen(dst_path) - 1] == '/');// 发送命令if (is_dir) {// 如果目标是目录,发送目录路径和文件名sprintf(command, "PUT %s|%s", dst_path, src_filename);} else {// 否则发送完整路径sprintf(command, "PUT %s", dst_path);}write(sock_fd, command, strlen(command));// 等待确认memset(buffer, 0, BUFFER_SIZE);bytes_read = read(sock_fd, buffer, BUFFER_SIZE - 1);if (bytes_read <= 0 || strcmp(buffer, "OK") != 0) {fprintf(stderr, "服务器拒绝请求\n");close(file_fd);close(sock_fd);exit(EXIT_FAILURE);}// 发送文件内容while ((bytes_read = read(file_fd, buffer, BUFFER_SIZE)) > 0) {write(sock_fd, buffer, bytes_read);}close(file_fd);// 显示完整的目标路径if (is_dir) {printf("文件上传完成: %s -> %s:%s%s\n", src_path, host, dst_path, (dst_path[strlen(dst_path) - 1] == '/') ? src_filename : "");} else {printf("文件上传完成: %s -> %s:%s\n", src_path, host, dst_path);}} else {  // GET - 从开发板下载文件// 发送命令sprintf(command, "GET %s", src_path);write(sock_fd, command, strlen(command));// 等待确认memset(buffer, 0, BUFFER_SIZE);bytes_read = read(sock_fd, buffer, BUFFER_SIZE - 1);if (bytes_read <= 0 || strcmp(buffer, "OK") != 0) {fprintf(stderr, "服务器拒绝请求\n");close(sock_fd);exit(EXIT_FAILURE);}// 检查目标路径是否是目录struct stat st;int is_dir = 0;if (stat(dst_path, &st) == 0) {is_dir = S_ISDIR(st.st_mode);}char final_path[BUFFER_SIZE];if (is_dir) {// 如果目标是目录,使用源文件名char *src_filename = basename((char *)src_path);sprintf(final_path, "%s/%s", dst_path, src_filename);} else {strcpy(final_path, dst_path);}// 打开文件file_fd = open(final_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);if (file_fd < 0) {perror("无法创建目标文件");close(sock_fd);exit(EXIT_FAILURE);}// 接收文件内容while ((bytes_read = read(sock_fd, buffer, BUFFER_SIZE)) > 0) {write(file_fd, buffer, bytes_read);}close(file_fd);printf("文件下载完成: %s:%s -> %s\n", host, src_path, final_path);}close(sock_fd);
}void usage() {printf("用法:\n");printf("  服务器模式 (在开发板上运行): mini_scp server\n");printf("  上传文件: mini_scp <本地文件> <用户名>@<主机>:<远程路径>\n");printf("  下载文件: mini_scp <用户名>@<主机>:<远程文件> <本地路径>\n");printf("\n");printf("例子:\n");printf("  mini_scp /home/user/file.txt root@192.168.10.50:/tmp/file.txt\n");printf("  mini_scp /home/user/file.txt root@192.168.10.50:/tmp/       # 上传到目录\n");printf("  mini_scp root@192.168.10.50:/etc/passwd ./passwd_copy\n");printf("  mini_scp root@192.168.10.50:/etc/passwd ./                  # 下载到当前目录\n");exit(EXIT_FAILURE);
}int main(int argc, char *argv[]) {if (argc < 2) {usage();}if (strcmp(argv[1], "server") == 0) {// 服务器模式scp_server();} else if (argc == 3) {char *src = argv[1];char *dst = argv[2];// 检查是否为下载操作if (strchr(src, '@') != NULL && strchr(src, ':') != NULL) {// 下载文件char host[256] = {0};char remote_path[BUFFER_SIZE] = {0};char *at_sign = strchr(src, '@');char *colon = strchr(src, ':');if (at_sign < colon) {strncpy(host, at_sign + 1, colon - at_sign - 1);strcpy(remote_path, colon + 1);scp_client(1, remote_path, dst, host);} else {usage();}} // 检查是否为上传操作else if (strchr(dst, '@') != NULL && strchr(dst, ':') != NULL) {// 上传文件char host[256] = {0};char remote_path[BUFFER_SIZE] = {0};char *at_sign = strchr(dst, '@');char *colon = strchr(dst, ':');if (at_sign < colon) {strncpy(host, at_sign + 1, colon - at_sign - 1);strcpy(remote_path, colon + 1);scp_client(0, src, remote_path, host);} else {usage();}} else {usage();}} else {usage();}return 0;
}
EOF# 编译Linux主机版本
gcc -o mini_scp mini_scp_v2.c# 交叉编译ARM版本
arm-linux-gnueabihf-gcc -static -o mini_scp_arm mini_scp_v2.c# 检查是否为静态链接
file mini_scp_arm
# 应显示"statically linked"# 复制到Ubuntu系统目录
sudo cp mini_scp /usr/local/bin/
sudo chmod 755 /usr/local/bin/# 复制到NFS
sudo cp mini_scp_arm ~/linux/nfs/rootfs/bin/mini_scp
sudo chmod 755 ~/linux/nfs/rootfs/bin/mini_scp

设置开发板开机自启动:

# 创建服务脚本
cat > /etc/init.d/scp_service << 'EOF'
#!/bin/sh
# SCP服务自启动脚本
mini_scp server &
EOFchmod 777 /etc/init.d/scp_service# 添加到启动脚本
echo "/etc/init.d/scp_service" >> /etc/init.d/rcS

 重启开发板:

 从ubuntu传输文件到开发板:

mini_scp demo1.db root@192.168.10.50:/home/

查看开发板: ,传输完成。


总结

优势

  1. 轻量级:程序体积小,资源占用少
  2. 无依赖:静态链接,不依赖开发板上的glibc版本
  3. 易用性:与标准SCP语法相似,易于使用
  4. 灵活性:支持文件上传和下载,支持目录自动创建

劣势

  1. 安全性:不提供加密和认证功能
  2. 功能:不支持递归复制目录等高级功能
  3. 稳定性:错误处理相对简单
http://www.xdnf.cn/news/571123.html

相关文章:

  • Java多线程深度解析:从核心机制到高阶实战
  • upload-labs通关笔记-第16关 文件上传之exif_imagetype绕过(图片马)
  • springcloud集成seata报错Error creating bean with name ‘globalTransactionScanner‘
  • 解决RedisTemplate的json反序列泛型丢失问题
  • SpringAI开发SSE传输协议的MCP Server
  • 安卓开发用到的设计模式(1)创建型模式
  • OpenCv高阶(十四)——LBPH人脸识别
  • 如何用 Qwen1.5-7B-Chat 模型打造高效轻量的 Python 智能助手(详细实操指南)
  • Windows在PowerShell或CMD运行 curl 命令报错 解决办法 (zx)
  • 如何支持Enhanced RTMP H.265(HEVC)
  • 蓝耘Ubantu服务器测试最新 PP-StructureV3 教程
  • linux 查看java的安装路径
  • Java面试问题基础篇
  • Wireshark 抓包工具使用
  • Visual Studio Code插件离线安装指南:从市场获取并手动部署
  • Android Framework学习八:SystemServer及startService原理
  • 鸿蒙开发——9.wrapBuilder与@BuilderParam对比解析
  • Oracle 11g post PSU Oct18 设置ssl连接(使用jks)
  • 拉普拉斯高斯(LoG)滤波器掩模的注意事项
  • 计及可再生能源不确定性的经济优化调度方法
  • AI与IT从业者的关系更似“进化催化剂“而非“职业终结者“
  • 太阳能电池IV测试设备AAA型AMG1.5太阳光模拟器
  • 道可云人工智能每日资讯|浙江省人民政府印发《关于支持人工智能创新发展的若干措施》
  • [特殊字符] 遇见Flask
  • 【HTML-4】HTML段落标签:构建内容结构的基础
  • 递归+反射+注解(动态拼接建表语句)
  • 机动车授权签字人有哪些权利和义务?
  • 【Element UI排序】JavaScript 的表格排序sortable=“custom“和 @sort-change
  • 欢乐熊大话蓝牙知识7:如何用蓝牙芯片实现一个 BLE 传感器节点?
  • SAR ADC 是选择先置位再比较,还是先比较再置位