基于libssh2的ssh远程执行/sftp传输C++库pssh
csdh11 2024-12-10 13:11 21 浏览
平时维护一大堆linux服务器,如何无需交互带密码远程执行命令?其实之前也调研过几种方案,比如直接调用plink获取其结果--后来发现这玩意非线程安全,无法多线程使用且bug很多。又比如QT自带的QSSH代码,我搞下来试了一下,一是也是非线程安全,二是这货是非阻塞式调用,门槛比较高,难受的很,所以放弃了,最终选定了libssh2。
不用脚本纯代码的话,libssh2无疑是一个好的选择。但这个库对新手不是很友好,网络上的例子也都是把官方给的几个例子抄来抄去毫无新意,不但繁琐而且有很多缺点,总结几个如下:
- 纯C库,用起来不是那么方便
- 非线程安全,无法线程执行
- 连接/执行没有断线重连功能
- sftp传输没有上传/下载文件夹的功能
- sftp传输没有短点续传的功能
- windows与linux之间sftp传输不支持路径以及文件名含有中文以及空格问题
- 远程执行返回You have mail/You have new mail的问题
- ssh命令过长偶尔导致\b \b \b..乱码的问题
等一系列问题,实在无法满足我想简单粗暴阻塞式执行命令以及传输文件、目录的需求,于是自己花了几个月封装了一个跨平台pssh库,致力于解决这些问题,主要功能以及更新记录如下:
- 增加了带空格的目录以及文件名支持
- 增加了中文支持
- 对断点续传上传和下载文件增加速度提示功能
- 增加了下载文件/上传文件/下载目录/上传目录的断点续传功能
- 已改造windows上传文件,下载文件,上传目录,下载目录,支持回调函数,目录支持进度
- 传输相关要从windows下psshsingle考,从linux考来的不对,因为有中文问题
- 已改造为单线程断线重连,不要卡住,目前实现思路是,send失败后,只重连一次,失败再返回,因为xsh一般都不是只执行一次,多次执行,上一次失败的话,重连一次,这次失败,下一次就会成功
- 本版本是多个channel版本,以后多线程用这个作为模板,原来的套一个pssh太臃肿,每次要写两遍函数
- 修改了recv处理,兼容centos系和ubuntu系列,ubuntu系列没有[,可连接127.0.0.1
- 单线程重连版,主要是为了防止线程过多,线程重连如果pssh多的话2倍线程,而且稳定一些
- xsh是send失败直接阻塞重连,recv因为有参数返回,所以还是返回值,在xsh里面阻塞重连
- xshlastinng是成功尝试多个channel,在循环lastting的时候用statuschannel判断阻塞重连
- 已经改造了close,xsh和xshlasting都可以closetrue
- 已经改造了xshlasting,可以做到closetrue后再open依然可以
- 已经改造了xsh,可以做到closetrue后再open依然可以
- 解决了深度openssl线程安全问题
- 增加了获取是否生成core文件功能,增加在/etc/profile增加配置等功能
- 增加切换目录,获取当前目录,查看远程进程,获取根目录使用比例是否在等常用功能
- 增加获取远程core文件"Core文件"<<"服务器地址"<<"所属进程"<<"生成时间"的list信息
- 增加了远程ini模式配置文件的增删改查功能
- 增加了给/etc/profile添加配置功能,内置判断是否已有配置
- 增加了close(true)功能,true情况下关闭自动重连,彻底关闭(两种模式);
- 修复了在高延迟情况下判断远程文件是否存在时间过长的bug(不再用sftp模式获取,使用ssh模式直接ll获取)
- 增加xshlasting模式自动重连功能,使用另一个xsh一直发送pwd探测,如果断开,则线程自动重连,重连成功后继续返回正确结果
- 增加了自动重连的功能,如果连接断开,则当时xsh返回错误,线程自动重连,如果下次已经连接上则下次获取正确结果
- 增加了与服务器连接断开时返回错误的功能,不会一直卡住
- 阻塞调用,xsh等结果获取完成才返回
- 增加了上传文件/下载文件/上传目录/下载目录功能
- 修复了xsh有时会返回You have mail/You have new mail的问题,过滤
- 修复了xsh有时命令过长会出现结果返回\b\b\b..乱码的问题,过滤
- 修改了xsh功能,任务错误都字符串返回#fail,包括连接断开
- 修改了connect,增加了连接超时功能,默认超时时间1秒,原来是20秒太长
- 增加了同一个ssh close再open功能
- 测试了多线程功能C++11线程,线程安全
- 增加了探测连接的功能.另起一个chanel一直探测,用于判断连接状态,该功能是xshlasting的基础,因为xshlasting不能判断断了,用这个bconnected判断
- 增加了支持域名功能(为了支持端口映射)
- 增加两种模式,一种正常获取xsh结果,一种持续获取xshlasting,xshlasting可用于tail -f收集日志到统一服务器
- 增加了xsh获取结果超时10秒返回错误功能
- 对于xsh获取结果失败(原因不限于连接断开,超时等),均返回#fail开头的字符串
主要代码如下:
/*
* 作者:李海龙2021-02-26
*/
class pssh {
public:
pstring strhost;
int iport;
pstring strpwd;
pstring struser;
pstring strerr;//最后一次错误描述
pssh();
pssh(pstring host,int port=22,pstring pwd="scfwq_325",pstring user="root");
void init(pstring host,int port=22,pstring pwd="scfwq_325",pstring user="root");
virtual ~pssh();
int open();//open一次就行,返回结果,不用自动重连,因为后边执行xsh会自动重连
int close(bool breal=false);//close(true)停止重连
/*
* xsh是send和recv的合集,获取top结果用top -b n 1
* 获取过程中如果断了,无论是send还是recv会自动重连一次,然后返回失败,如果重连成功,则下次会正确,这样能保证返回失败提示
* 如果是主动closetrue则会返回#fail
*/
pstring xsh(pstring cmd,pstring type="data");
//send失败一次,自带一次重连,反正失败了也不在乎那几秒了,如果重连成功,重新发送一次命令,如果失败,返回,这样既重连了,也能返回看结果
int send(pstring cmd,pstring type="data");
//接收有多种状态,返回值,在xsh中统一处理自动重连,带失败自动重连一次,与send一样,有10秒超时
int recv(pstring &data,pstring type="data");
/* 使用方式
* ptrans->uploadThread(pathFull,this->strpwdremote,
* std::bind(&MainWindow::showstr,this,placeholders::_1));
* //停止时用close(true)
*/
//持续结果输出,例如tail -f,过程输出,也可以执行别的命令
//停止时用close(true)
int xshlasting(pstring cmd, std::function<void(pstring)> fun=funShow);
/* 用两个channel实现的,使用方式
* ptrans->uploadThread(pathFull,this->strpwdremote,
* std::bind(&MainWindow::showstr,this,placeholders::_1));
*/
int xshlastingThread(pstring cmd, std::function<void(pstring)> fun=funShow);
//判断远程文件是否有key,比如判断core是否启用bHasKeyInRemoteFile("/etc/profile","ulimit");
bool bHasKeyInRemoteFileBridge(pstring host,pstring path = "/etc/profile", pstring key = "ulimit");
bool ping(pstring host);
presult getTimeLastReboot();
//"Core文件"<<"服务器地址"<<"所属进程"<<"生成时间"的list
pvector<pliststring> getCoreInfo(pstring strpath="/");
//判断远程文件是否有key,比如判断core是否启 bHasKeyInRemoteFile("/etc/profile","ulimit");
bool bHasKeyInRemoteFile(pstring path = "/etc/profile", pstring key = "ulimit");
//获取根目录占用比例
presult getUsageRoot();
bool cd(pstring path);
presult getContentFromRemoteFile(pstring path);
pstring getIPRemote();
pstring pwd();
//从源码目录传过去,改权限,获取结果,已包括断开连接的判断,四个
pstring getconf(pstring path, pstring section, pstring key, bool bupload = false);
bool delconf(pstring path, pstring section, pstring key, bool bupload = false);
bool addconf(pstring path, pstring section, pstring key, pstring val, bool bupload = false);
bool setconf(pstring path, pstring section, pstring key, pstring val, bool bupload = false);
ppair<int,ptime> getTimeNow();
//重命名
int mv(pstring strold,pstring strnew);
//获取文件大小
longlong getFileSize(pstring strPathFull);
//获取类型 dir file lnk noaccess,失败返回空用sftp用以提高速度
pstring getPathTypeRemote(pstring strPath);
//删除文件或目录,支持带空格的
int rm(pstring strPathFullRemote);
//sftp创建文件 支持带空格
int touch(pstring strPathFullRemote);
//远程递归创建目录sftp速度快//错误返回小于0,已存在也返回错误 -31表示已存在,但是
//根据一般业务情况,改进为,如果有同名目录,返回创建成功,如要判断之前是否已存在,单独用isExsistdir
int mkdirp(pstring strPathFullRemote);
//远程创建目录sftp速度快//错误返回小于0,已存在也返回错误 -31表示已存在,但是
//根据一般业务情况,改进为,如果有同名目录,返回创建成功,如要判断之前是否已存在,单独用isExsistdir
int mkdir(pstring strPathFullRemote);
//判断有没有上述目录
bool isExsistDirRemote(pstring strPathDirRemote);
//判断有没有上述文件
bool isExsistFileRemote(pstring strPathFullFile);
//获取名字和类型
pmap<pstring, pstring> getAllInDirRemote(pstring strPathDirRemote);
//获取名字,类型和大小,最后修改时间,sftp方式
plist<pliststring> getAllWithSizeInDirRemote(pstring strPathDirRemote);
//这获取的是全路径
void getAllFilesAndPathsRecursionRemote(pstring path, pliststring &lfile, pliststring &ldir);
//lnk也要下载,下载后就是原来指向的文件,scp也是这样的,所以会与原来大小不一,但是要与scp一样就行,目前与scp不一样大小查原因
int downloadDir(pstring strPathFullLocal,pstring strPathFullRemote,
std::function<void(pliststring)> fun=showProcessDir);
//上传libssh2目录慢很多,两分钟,但下载很快,查原因
int uploadDir(pstring strPathFullLocal,pstring strPathFullRemote,
std::function<void(pliststring)> fun=showProcessDir);
//两个都是全路径--经测试windows版的上传超过10秒后有时就不支持内置断点续传,老老实实写seek吧!,断点续传的逻辑已经写好了,llpos=0是从头传
int uploadFileOnce(pstring strPathFullLocal,pstring strPathFullRemote,
std::function<void(pliststring)> fun=showProcessFile,
longlong llpos=0);
//uploadfile断点续传逻辑已经写好了,但是在centos6.8下sftpappend会失效,可能就像提示一样:APPEND doesn't have any effect on OpenSSH servers
//测试在centos7下没问题,但是centos6就不行,所以考虑uploadfile时,断了的话,直接从头开始,因为本来断点续传也是偶发事件,改为断线重传,下载还是断点续传
//自动断点续传直到正确传完为止--在centos6.8下有时候断点续传有问题---查原因
int uploadFile(pstring strPathFullLocal,pstring strPathFullRemote,
std::function<void(pliststring)> fun=showProcessFile);
//两个都是全路径--经测试只有windows版的download过了10秒后不支持断点续传,10秒以内可以,所以要改造,断点续传的逻辑已经写好了,llpos=0是从头传
int downloadFileOnce(pstring strPathFullLocal, pstring strPathFullRemote,
std::function<void(pliststring)> fun=showProcessFile,
longlong llpos=0);
//自动断点续传直到正确传完为止
int downloadFile(pstring strPathFullLocal, pstring strPathFullRemote,
std::function<void(pliststring)> fun=showProcessFile);
int showDir(pstring strPathDirRemote);
//获取类型 dir file lnk noaccess,失败返回空用xsh速度太慢,200ms
pstring getTypePathRemoteUseXsh(pstring strPath);
//远程递归创建目录,速度慢,要改成sftp创建目录
int mkdirpUseXsh(pstring strPathFullRemote);
bool getStatusConnect();
friend ostream &operator<<(ostream &os, pssh x) {
os << x.strhost << ":" << x.iport << "@" << x.struser
<< " connect status: " << x.bConnected;
return os;
}
//下边这两个是为了持续获取搞的
void recvLasting(std::function<void(pstring)> fun);
LIBSSH2_CHANNEL *pchannelData;
LIBSSH2_CHANNEL *pchannelStatus;
//新加的
bool bConnected;
private:
LIBSSH2_SFTP *sftp_session;
pstring strCmdNow;
bool bstop;
//原来的
// int m_socket;
ptcp tcp;
LIBSSH2_SESSION *psession;
bool connect(const char *szIp, int nPort = 22);
bool login(const char *szUserName, const char *szPassword);
// 返回值表示渠道的序号。如果返回-1,创建失败;
LIBSSH2_CHANNEL *createChannel(const char *szChannelTerm = "vanilla");
bool write(LIBSSH2_CHANNEL *channel, const char *szData);
int waitsocket(int socket_fd, LIBSSH2_SESSION *session);
};
其实我也知道大家对我拙劣的封装并不感兴趣,只关心好不好用,怎么用的问题,下边给大家展示一下:
普通远程执行:
//初始化 地址 端口 密码 用户名
pssh ssh("192.168.133.129",22,"123.asdf","root");
//连接linux服务器
hlog(ssh.open());
//同时做个多线程一直执行pwd命令
std::thread([]{
pssh ssh2("192.168.133.129",22,"123.asdf","root");
hlog(ssh2.open());
while(1)
{
hlog(ssh2.xsh("pwd"));
plib::sleep(1000);
}
}).detach();
//获取结果
pstring stres=ssh.xsh("ifconfig|grep inet");\
//日志打印
hlog(stres);
stres=ssh.xsh("pwd");
hlog(stres);
while(1)
{
plib::sleep(1000);
}
结果如下:
?
持续远程执行获取(获取tail -f 这种)
//同时做个多线程一直往远程服务器/root/test.log下一秒写一次数据,即模拟日渐增长的日志文件
std::thread([]{
pssh ssh2("192.168.133.129",22,"123.asdf","root");
hlog(ssh2.open());
//先把原来的删掉
hlog(ssh2.xsh("rm -f /root/test.log"));
int count=0;
while(1)
{
pstring strcmd="echo '"+plib::toString(count+1)+": qq1415532825'>>/root/test.log";
(ssh2.xsh(strcmd));
plib::sleep(1000);
count++;
}
}).detach();
plib::sleep(1000);
//初始化 地址 端口 密码 用户名
pssh ssh("192.168.133.129",22,"123.asdf","root");
//连接linux服务器
hlog(ssh.open());
//新线程内持续获取日志,将结果通过回调函数的形式回放到需要的函数内
ssh.xshlastingThread("tail -f /root/test.log",funShowInfo);
//5秒后关闭持续获取
plib::sleep(5000);
//close true代表停止自动重连,彻底停止,close false代表服务器异常断开后会断线重连继续获取
hlog(ssh.close(true));
while(1)
{
plib::sleep(1000);
}
关于此种模式,不用说大家也知道可以用来干什么,日志同步啊,日志收集,日志监控啊!甚至都不用部署客户端,简直不要太爽!
sftp文件上传、下载
pssh ssh("192.168.133.129",22,"123.asdf","root");
hlog(ssh.open());
//测试上传下载,顺便把中文也测试了
hlog(ssh.uploadFile("d:/setups/协和2.17.0.apk","/home/xlfd/"));
hlog(ssh.downloadFile("d:/test/协和下载.apk","/home/xlfd/协和2.17.0.apk"));
结果如下:
?
sftp目录递归上传、下载
测试目录:
?
pssh ssh("192.168.133.129",22,"123.asdf","root");
hlog(ssh.open());
//测试上传下载,顺便把中文也测试了
// hlog(ssh.uploadFile("d:/setups/协和2.17.0.apk","/home/xlfd/"));
// hlog(ssh.downloadFile("d:/test/协和下载.apk","/home/xlfd/协和2.17.0.apk"));
hlog(ssh.uploadDir("d:/setups/Debuggers","/root"));
hlog(ssh.downloadDir("D:/","/root/Debuggers/"));
结果如下:
?
?
当然以上功能都有断点续传功能,传输功能都带回调函数,方便与QT界面外部调用等集成,以后我会基于该库专门写一个sftp传输工具。
?
相关推荐
- 探索Java项目中日志系统最佳实践:从入门到精通
-
探索Java项目中日志系统最佳实践:从入门到精通在现代软件开发中,日志系统如同一位默默无闻却至关重要的管家,它记录了程序运行中的各种事件,为我们排查问题、监控性能和优化系统提供了宝贵的依据。在Java...
- 用了这么多年的java日志框架,你真的弄懂了吗?
-
在项目开发过程中,有一个必不可少的环节就是记录日志,相信只要是个程序员都用过,可是咱们自问下,用了这么多年的日志框架,你确定自己真弄懂了日志框架的来龙去脉嘛?下面笔者就详细聊聊java中常用日志框架的...
- 物理老师教你学Java语言(中篇)(物理专业学编程)
-
第四章物质的基本结构——类与对象...
- 一文搞定!Spring Boot3 定时任务操作全攻略
-
各位互联网大厂的后端开发小伙伴们,在使用SpringBoot3开发项目时,你是否遇到过定时任务实现的难题呢?比如任务调度时间不准确,代码报错却找不到方向,是不是特别头疼?如今,随着互联网业务规模...
- 你还不懂java的日志系统吗 ?(java的日志类)
-
一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...
- 谈谈枚举的新用法--java(java枚举的作用与好处)
-
问题的由来前段时间改游戏buff功能,干了一件愚蠢的事情,那就是把枚举和运算集合在一起,然后运行一段时间后buff就出现各种问题,我当时懵逼了!事情是这样的,做过游戏的都知道,buff,需要分类型,且...
- 你还不懂java的日志系统吗(javaw 日志)
-
一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...
- Java 8之后的那些新特性(三):Java System Logger
-
去年12月份log4j日志框架的一个漏洞,给Java整个行业造成了非常大的影响。这个事情也顺带把log4j这个日志框架推到了争议的最前线。在Java领域,log4j可能相对比较流行。而在log4j之外...
- Java开发中的日志管理:让程序“开口说话”
-
Java开发中的日志管理:让程序“开口说话”日志是程序员的朋友,也是程序的“嘴巴”。它能让程序在运行过程中“开口说话”,告诉我们它的状态、行为以及遇到的问题。在Java开发中,良好的日志管理不仅能帮助...
- OS X 效率启动器 Alfred 详解与使用技巧
-
问:为什么要在Mac上使用效率启动器类应用?答:在非特殊专业用户的环境下,(每天)用户一般可以在系统中进行上百次操作,可以是点击,也可以是拖拽,但这些只是过程,而我们的真正目的是想获得结果,也就是...
- Java中 高级的异常处理(java中异常处理的两种方式)
-
介绍异常处理是软件开发的一个关键方面,尤其是在Java中,这种语言以其稳健性和平台独立性而闻名。正确的异常处理不仅可以防止应用程序崩溃,还有助于调试并向用户提供有意义的反馈。...
- 【性能调优】全方位教你定位慢SQL,方法介绍下!
-
1.使用数据库自带工具...
- 全面了解mysql锁机制(InnoDB)与问题排查
-
MySQL/InnoDB的加锁,一直是一个常见的话题。例如,数据库如果有高并发请求,如何保证数据完整性?产生死锁问题如何排查并解决?下面是不同锁等级的区别表级锁:开销小,加锁快;不会出现死锁;锁定粒度...
- 看懂这篇文章,你就懂了数据库死锁产生的场景和解决方法
-
一、什么是死锁加锁(Locking)是数据库在并发访问时保证数据一致性和完整性的主要机制。任何事务都需要获得相应对象上的锁才能访问数据,读取数据的事务通常只需要获得读锁(共享锁),修改数据的事务需要获...
- 一周热门
- 最近发表
- 标签列表
-
- mydisktest_v298 (34)
- document.appendchild (35)
- 头像打包下载 (61)
- acmecadconverter_8.52绿色版 (39)
- word文档批量处理大师破解版 (36)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- parsevideo (33)
- 个人网站源码 (37)
- centos7.4下载 (33)
- mysql 查询今天的数据 (34)
- intouch2014r2sp1永久授权 (36)
- 先锋影音源资2019 (35)
- jdk1.8.0_191下载 (33)
- axure9注册码 (33)
- pts/1 (33)
- spire.pdf 破解版 (35)
- shiro jwt (35)
- sklearn中文手册pdf (35)
- itextsharp使用手册 (33)
- 凯立德2012夏季版懒人包 (34)
- 反恐24小时电话铃声 (33)
- 冒险岛代码查询器 (34)
- 128*128png图片 (34)
- jdk1.8.0_131下载 (34)