嵌入式Linux:移植使用scp指令
文章目录
前言
一、mini_scp?
二、实现原理
三、命令协议
四、代码指令实现
总结
优势
劣势
前言
在嵌入式linux开发中经常需要文件传输,我现在使用NFS来实现ubuntu和嵌入式Linux开发板的文件共享传输,但是还是想要使用scp指令,快捷一点,就想着移植openssh,下载源码解压编译后,在开发板上生成密钥报错,如下:
搜索发现,重新移植根文件系统来升级GLIC,哇,就很离谱啊,,我就是想使用个scp指令而已,搞这么麻烦干嘛,你干嘛,哎呦!!!!!!!,,,,,,但是无所谓,不更新根文件系统,我直接手动创建一个mini_scp,在开发板和主机之间轻松传输文件。
一、mini_scp?
仿照scp的实现,静态链接,不依赖开发板上的glibc版本,轻量级,资源占用小,使用与标准SCP相似的语法,便于使用,支持文件上传和下载。
二、实现原理
mini_scp工具基于TCP套接字通信,采用客户端-服务器架构:
- 服务器端运行在开发板上,监听指定端口,我是用的是1234端口
- 客户端运行在开发机上,ubuntu连接到开发板,可设置开发板开机自启动并在后台运行
- 通过简单的命令协议实现文件传输,类似scp执行
三、命令协议
- PUT <路径>|<文件名> - 上传文件到开发板
- GET <路径> - 从开发板下载文件
- OK - 命令确认
- 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/
查看开发板: ,传输完成。
总结
优势
- 轻量级:程序体积小,资源占用少
- 无依赖:静态链接,不依赖开发板上的glibc版本
- 易用性:与标准SCP语法相似,易于使用
- 灵活性:支持文件上传和下载,支持目录自动创建
劣势
- 安全性:不提供加密和认证功能
- 功能:不支持递归复制目录等高级功能
- 稳定性:错误处理相对简单