文章目录
前言
在嵌入式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;
// 创建socket
server_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];
// 创建socket
sock_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 &
EOF
chmod 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语法相似,易于使用
- 灵活性:支持文件上传和下载,支持目录自动创建
劣势
- 安全性:不提供加密和认证功能
- 功能:不支持递归复制目录等高级功能
- 稳定性:错误处理相对简单