嵌入式Linux:移植使用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;
    
    // 创建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/

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


总结

优势

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

劣势

  1. 安全性:不提供加密和认证功能
  2. 功能:不支持递归复制目录等高级功能
  3. 稳定性:错误处理相对简单
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值