用libssh2实现sftp文件传输C++函数接口

实习领导给了个任务,让我写一个接口,用sftp来传输文件的接口。我完全不懂是什么意思,但是先答应下来研究一下。

经过一番了解,知道sftp≈ssh,遂开始研究ssh。实验的对象是我自己的win10电脑和电脑上的ubuntu虚拟机。一般win会自带ssh client。在win上正确安装了ssh server之后,可以通过shell命令实现文件的传输。ssh每个用户都有一个用户名和密码,用户名形式如 name@ip ,在windows上这个用户是自动创建好的,它的用户名和密码很疑惑,详情见如何通过SSH远程连接到Windows系统指南_windows使用ssh连接远程服务器-CSDN博客

搞明白之后我就开始试图写一个程序,在程序中调用命令行来完成文件的传输(因为之前做过一个在线oj用的是这个思路),但实际上很难用这种方法做到,因为命令行涉及文件的权限,输入密码等问题,往往将一行命令运行是不能达到效果的。

在这种背景下,发现了libssh2.这个C++库据称可以实现我想要的效果,于是跟随网上的指导进行了安装,这里贴出一篇很好的文章,从下载安装测试到基础的讲解都有使用libssh2建立安全的SSH连接:C++开发者的综合指南_windows 下libssh的下载以及使用-CSDN博客

这里插播一下关于linux C++编译运行的一点小插曲。

我以前唯一用过的三方库就是qt,而qt creator自然是和qt的库相连的。直到之前做在线oj才遇到了库安装好了之后,编译的时候却找不到这种情况。这次也遇到了同样的问题。这个时候可以手动用命令行编译,在命令中加上 -l 库名。举个例子:

g++ main.cc -o main -l ssh2

这句代码的意思就是,用g++编译mian.cc,将结果的可执行文件命名为main,并且使用了libssh2.(这种解决方法虽然方便,但是并不是一劳永逸的,每次都手动编译还是很烦的,这是一个需要我去解决的问题。)

./mian

这句的意思是运行上面得到的可执行文件。这次遇到的第二个问题就出现在这里。可执行文件运行后会显示自己连接不上动态库。文件在运行时找不到libssh2的某个动态库。经过一番搜寻找到一个解决的办法:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

具体详情可见动态库加载失败:error while loading shared libraries: xxx.so: cannot open shared object file: No such file o-CSDN博客

然后就可以正常找到动态库了。但是这个方法同样是暂时的,仅在此时使用的这个终端生效,要想永久生效还需要使用上方文章中提到的别的方法。

在把编译运行的各个环节都打通之后,本以为终于可以正常运行了,最终却发现还是不行……不管是网上找到的简短示例还是libssh2官方的example都无法正确运行。有的是代码里总是有很多部分显示未定义、有的是函数返回的错误值官网没有(我的libssh2_session_handshake()这个函数总是返回-43,而官网并没有定义-43是什么含义),甚至在安装了1.11版本的libssh2之后连make check都开始fail。于是陷入疯狂的寻找,始终没有结果。在csdn上看到一位老哥说libssh比libssh2稳定有效的多,libssh2总是有很多莫名其妙的错误。可惜的是,经过一番研究,libssh并不支持sftp。

也就是到这个时候我才知道ssh并不完全等于sftp。sftp是ssh的协议中的一种。

最终,我顺手去百度了一下那几个未定义的东西,然后惊讶的发现它们并不是libssh2中的内容,是别的C++语言中的常用库里的内容。不知道为什么那些代码里没有在开头#include。在将它们一一include之后,程序终于可以顺利的跑通了!

我参考的代码来源:不使用SSH,也不依赖其他库,需要在C++程序中实现SFTP协议。_sftp c++-CSDN博客

这份代码的功能是将远程端指定位置的文件下载到本地并且写入本地的一个文件中。这和我的需求有一些不符,因为它要求这里本来就应该有一个文件,而我是需要创建出不存在的文件/文件夹。关键是这两部分:

if (access(local_path, F_OK) == -1)//#include <unistd.h> #include <stdio.h>
	{
		mkdir(local_path,00777);
        std::cerr << "文件夹不存在,已自动创建" << std::endl;
	}
std::ofstream fout(local_file, std::ios::out | std::ios::binary | std::ios::trunc);
//std::ios::trunc代表不存在则创建

于是进行了修改,并把代码封装成了一个函数。以下是代码全貌:

#include <iostream>
#include <fstream>
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <stdio.h>

int sftpDownload(const char *hostname,const char *user,const char *password,const char *remote_path,const char *file_name,const char* local_path){
    //远端ip,远端用户名,远端密码,远端路径,文件名,本地路径
    // 初始化 libssh2 库
    int rc = libssh2_init(0);
    if (rc != 0) {
        std::cerr << "libssh2 initialization failed" << std::endl;
        return 1;
    }

    // 建立 TCP 连接
    int port = 22;
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        std::cerr << "socket failed" << std::endl;
        libssh2_exit();
        return 1;
    }
    struct sockaddr_in sin;
    sin.sin_family     = AF_INET;
    sin.sin_port       = htons(port);
    sin.sin_addr.s_addr= inet_addr(hostname);
    if (connect(sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)) != 0) {
        std::cerr << "connect failed" << std::endl;
        close(sock);
        libssh2_exit();
        return 1;
    }

    // 建立 SFTP 会话
    LIBSSH2_SESSION *session = libssh2_session_init();
    if (!session) {
        std::cerr << "libssh2_session_init failed" << std::endl;
        close(sock);
        libssh2_exit();
        return 1;
    }
    rc = libssh2_session_handshake(session, sock);
    if (rc != 0) {
        std::cerr << "libssh2_session_handshake failed" << std::endl;
        close(sock);
        libssh2_session_free(session);
        libssh2_exit();
        return 1;
    }

    rc = libssh2_userauth_password(session, user, password);
    if (rc != 0) {
        std::cerr << "libssh2_userauth_password failed" << std::endl;
        close(sock);
        libssh2_session_free(session);
        libssh2_exit();
        return 1;
    }
    LIBSSH2_SFTP *sftp = libssh2_sftp_init(session);
    if (!sftp) {
        std::cerr << "libssh2_sftp_init failed" << std::endl;
        close(sock);
        libssh2_session_free(session);
        libssh2_exit();
        return 1;
    }

    // 下载远程文件到本地
    std::string rf= remote_path ;
    rf= rf + file_name;
    const char *remote_file = rf.c_str();
    std::string lf= local_path ;
    lf= lf + file_name;
    const char *local_file = lf.c_str();
    LIBSSH2_SFTP_HANDLE *handle =
        libssh2_sftp_open(sftp, remote_file, LIBSSH2_FXF_READ, 0);
    if (!handle) {
        std::cerr << "libssh2_sftp_open failed" << std::endl;
        libssh2_sftp_shutdown(sftp);
        close(sock);
        libssh2_session_free(session);
        libssh2_exit();
        return 1;
    }
    
    if (access(local_path, F_OK) == -1)
	{
		mkdir(local_path,00777);
        std::cerr << "文件夹不存在,已自动创建" << std::endl;
	}

    std::ofstream fout(local_file, std::ios::out | std::ios::binary | std::ios::trunc);
    if (!fout.good()) {
        std::cerr << "Failed to open local file for writing" << std::endl;
        libssh2_sftp_close(handle);
        libssh2_sftp_shutdown(sftp);
        close(sock);
        libssh2_session_free(session);
        libssh2_exit();
        return 1;
    }
    char buffer[1024];
    int len = 0;
    while ((len = libssh2_sftp_read(handle, buffer, sizeof(buffer))) > 0) {
        fout.write(buffer, len);
    }
    fout.close();
    libssh2_sftp_close(handle);

    // 关闭 SFTP 会话和其他资源
    libssh2_sftp_shutdown(sftp);
    libssh2_session_disconnect(session, "Normal Shutdown");
    libssh2_session_free(session);
    close(sock);
    libssh2_exit();
    return 0;

}

int main(int argc, char *argv[]) {
    int rc=sftpDownload("远端ip地址","用户名","密码","远端文件夹地址","文件名","本地文件夹");
    if(rc!=0){
        std::cerr << "传输失败" << std::endl;
        return 1;
    }
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值