首页 > 代码库 > SFTP客户端代码示例

SFTP客户端代码示例

SFTP客户端代码示例

环境:libssh2 1.4.3、zlib-1.2.8、openssl-1.0.1g

Author: Kagula

最后更新日期:2014-5-18

         从http://www.libssh2.org/下载libssh2-1.4.3.tar.gz文件,解压后打开libssh2.dsw文件升级项目到VisualStudio 2013,里面有两个项目,只要编译libssh2项目就可以了。编译前需要添加zlib和openssl的头文件和库文件链接位置,如果编译libssh2提示找不到msvcrt.lib,为链接库添加下面的路径

C:\Program Files (x86)\Microsoft VisualStudio 12.0\VC\lib

提示找不到ws2_32.lib或odbc32.lib,添加下面的链接路径

C:\Program Files (x86)\MicrosoftSDKs\Windows\v7.1A\Lib

编译通过后文件输出到\libssh2-1.4.3\win32\Release_lib路径下

下面是SFTP客户端示例代码

#include "SFTP_Libssh2.h"
#include <iostream>

int main(int argc, char* argv[])
{
	//下面的代码只要在进程初始化的时候执行
	kagula::network::SFTP_Init();

	//测试SFTP链接
	kagula::network::SFTP_Libssh2* client = kagula::network::SFTP_Libssh2::Inst();
	std::string ip = "192.168.19.130";
	uint16_t port = 22;
	std::string usr = "kagula";
	std::string pwd = "123456";
	if (false == client->IsAbilityConn(ip, port, usr, pwd))
	{
		std::cout << client->strLastError << std::endl;
		return -1;
	}

	//测试文件上传,d:\\temp\\a.html
	if (0 != client->upload(ip, 22, usr, pwd, "d:\\temp\\a.html", "/home/kagula/a.html"))
	{
		std::cout << "Error:" << client->strLastError << std::endl;
	} else
	{
		std::cout << client->strLastError << std::endl;
	}
	

	//测试文件下载
	if (0 != client->download(ip, 22, usr, pwd, "/home/kagula/a.html","d:\\temp\\b.html" ))
	{
		std::cout << "Error:" << client->strLastError << std::endl;
	}
	else
	{
		std::cout << client->strLastError << std::endl;
	}

	//进程准备结束,释放资源的时候,运行下面的代码
	kagula::network::SFTP_Exit();
	return 0;
}

SFTP_Libssh2.h

#pragma once

#include <string>
#include <atomic>

/*
功能:SFTP协议的文件传输功能
最后更新日期:2014-5-17
简介:借助Libssh2库很容易实现sftp,ssh2客户端,这里给出
      如何实现Sftp客户端的代码
测试环境:Windows 8.1 64bit、Visual Studio 2013 Professional SP1
       OpenSSL 1.0.1g、zlib-1.2.8、libssh2  1.4.3
       Win32控制台项目
注意:动态链接需要把“libssh2.dll”文件复制到当前项目路径下
说明:原来的代码支持多线程,从应用程序抽出来的时候简化了,
	 你可以修改代码使它同时支持上传或下载多个文件。
建议:[1]第三方库直接下载源代码自己编译免得库由于编译器版本的
     不同或设置的不同链接的时候一大堆麻烦。
	 [2]读懂代码根据项目需求作相应修改
补充阅读资料:
《使用libssh2库实现支持密码参数的ssh2客户端》
http://blog.chinaunix.net/uid-24382173-id-229823.html
*/
namespace kagula {
	namespace network {
		int SFTP_Init();
		void SFTP_Exit();

		class SFTP_BKCall
		{
		public:
			/* progress返回值范围[0.0,1.0] */
			virtual void OnProgress(float progress) = 0;
		};

		class SFTP_Libssh2
		{
		public:
			static SFTP_Libssh2* Inst()
			{
				static SFTP_Libssh2 inst;

				return &inst;
			}

			/*
			入口参数使用说明
			ip:	  就填一个IP地址就好了,例如“127.0.0.1”。
			port: 端口,SFTP服务器默认端口为22。
			username:
			password:
			sftppath: 远程路径“/”开头 ,例如“/a.jpg”
			localpath: 本地路径,例如“d:\\temp\\test.jpg”
			strLastError: 错误信息

			出口参数
			返回不等于零,代表失败!
			*/
			int upload(std::string ip, unsigned short port, std::string username,
				std::string password, std::string localpath, std::string remotepath);
			int download(std::string ip, unsigned short port, std::string username,
				std::string password, std::string sftppath, std::string localpath);

			//测试SFTP服务器是否可以链接
			bool IsAbilityConn(std::string ip, unsigned short port, std::string username,
				std::string password);

			//设置回掉函数
			void SetBKCall(SFTP_BKCall *bkCall)	{ m_bkCall = bkCall; }

			//存放最近的错误信息
			std::string   strLastError;

			//用于停止当前上传或下载线程
			void stop() { m_isBreak.store(true); }
		private:
			SFTP_Libssh2() :m_bkCall(NULL) { m_isBreak.store(false); };//防止直接初始化
			SFTP_Libssh2(const SFTP_Libssh2&);                 //防止拷贝复制
			SFTP_Libssh2& operator=(const SFTP_Libssh2&);      //防止分配(运算符函数的调用)

			SFTP_BKCall  *m_bkCall;			
			std::atomic_bool m_isBreak; //带读写保护的bool值
		};
	}
}


SFTP_Libssh2.cpp

//SFTP_Libssh2.cpp文件清单
#include "SFTP_Libssh2.h"

#include <libssh2.h>
#include <libssh2_sftp.h>

#ifdef HAVE_WINSOCK2_H
#include <winsock2.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>

#include <sstream>
#include <iomanip>

#pragma comment(lib, "ws2_32.lib")

#pragma comment(lib, "libeay32.lib")  
#pragma comment(lib, "libssh2.lib")  

namespace kagula {
	namespace network
	{
		//初始化进程的时候调用
		//如果非0表示初始化失败!
		int SFTP_Init()
		{
			WSADATA wsadata;
			int rc = WSAStartup(MAKEWORD(2, 0), &wsadata);
			if (rc != 0) {
				return rc;
			}

			rc = libssh2_init(0);

			return rc;
		}

		//进程结束的时候调用
		void SFTP_Exit()
		{
			libssh2_exit();

			WSACleanup();
		}

		bool SFTP_Libssh2::IsAbilityConn(std::string ip, unsigned short port, std::string username,
			std::string password)
		{
			unsigned long hostaddr;
			struct sockaddr_in sin;
			const char *fingerprint;
			LIBSSH2_SESSION *session;
			int rc;
			bool bR = false;
			FILE *local;
			LIBSSH2_SFTP *sftp_session;
			LIBSSH2_SFTP_HANDLE *sftp_handle;

			hostaddr = inet_addr(ip.c_str());//hostaddr = htonl(0x7F000001);


			//新建连接
			int sock = socket(AF_INET, SOCK_STREAM, 0);

			sin.sin_family = AF_INET;
			sin.sin_port = htons(port);
			sin.sin_addr.s_addr = hostaddr;
			if (connect(sock, (struct sockaddr*)(&sin),
				sizeof(struct sockaddr_in)) != 0) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "]failed to connect" << ip << "!" << std::endl;
				strLastError = ostr.str();

				return bR;
			}

			//新建对话实例
			session = libssh2_session_init();
			if (!session)
			{
				closesocket(sock);
				return bR;
			}

			//设置调用阻塞
			libssh2_session_set_blocking(session, 1);

			//进行握手
			rc = libssh2_session_handshake(session, sock);
			if (rc) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "]Failure establishing SSH session: " << rc << std::endl;
				strLastError = ostr.str();

				libssh2_session_free(session); closesocket(sock);
				return bR;
			}

			//检查主机指纹
			std::ostringstream ostr;
			fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
			ostr << "Fingerprint: ";
			for (int i = 0; i < 20; i++) {
				unsigned char c = fingerprint[i];
				int nT = c;
				ostr << std::hex << std::setw(2) << std::setfill('0') << nT;
			}
			strLastError = ostr.str();

			//通过密码验证登陆用户身份
			if (libssh2_userauth_password(session, username.c_str(), password.c_str())) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "]Authentication by password failed." << std::endl;
				strLastError = ostr.str();
				goto shutdown;
			}

			sftp_session = libssh2_sftp_init(session);

			if (!sftp_session) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "]Unable to init SFTP session " << std::endl;
				strLastError = ostr.str();

				goto shutdown;
			}

			bR = true;


			libssh2_sftp_shutdown(sftp_session);

		shutdown:
			libssh2_session_disconnect(session,
				"Normal Shutdown, Thank you for playing");
			libssh2_session_free(session);
			closesocket(sock);
			return bR;
		}

		/*
		源码参考地址
		http://www.libssh2.org/examples/sftp_write.html
		*/
		int SFTP_Libssh2::upload(std::string ip, unsigned short port, std::string username, std::string password,
			std::string localpath, std::string remotepath)
		{
			if (ip.length()<1 || username.length()<1 || password.length()<1 || localpath.length()<1 || remotepath.length()<1)
			{
				return -1;
			}

			int nR = 0;
			unsigned long hostaddr;
			struct sockaddr_in sin;
			const char *fingerprint;
			LIBSSH2_SESSION *session;
			int rc = -1;
			FILE *local = NULL;
			LIBSSH2_SFTP *sftp_session;
			LIBSSH2_SFTP_HANDLE *sftp_handle;

			hostaddr = inet_addr(ip.c_str());//hostaddr = htonl(0x7F000001);

			if (fopen_s(&local, localpath.c_str(), "rb") != 0) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "]Can't open local file " << localpath << std::endl;
				strLastError = ostr.str();

				return -2;
			}

			//取待上传文件整个size.
			fseek(local, 0, SEEK_END);
			size_t filesize = ftell(local);//local file大小,在readFromDisk中被引用
			fseek(local, 0, SEEK_SET);//文件指针重置到文件头

			//新建连接
			int sock = socket(AF_INET, SOCK_STREAM, 0);

			sin.sin_family = AF_INET;
			sin.sin_port = htons(port);
			sin.sin_addr.s_addr = hostaddr;
			if (connect(sock, (struct sockaddr*)(&sin),
				sizeof(struct sockaddr_in)) != 0) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] failed to connect " << ip << std::endl;
				strLastError = ostr.str();

				fclose(local);
				return -3;
			}


			//创建会话实例
			session = libssh2_session_init();
			if (!session)
			{
				fclose(local); closesocket(sock);
				return -4;
			}

			//阻塞方式调用libssh2
			libssh2_session_set_blocking(session, 1);

			//进行握手
			rc = libssh2_session_handshake(session, sock);
			if (rc) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] Failure establishing SSH session:" << rc << std::endl;
				strLastError = ostr.str();

				fclose(local); libssh2_session_free(session); closesocket(sock);
				return -5;
			}

			//获取主机指纹
			std::ostringstream ostr;
			fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
			ostr << "Fingerprint: ";
			for (int i = 0; i < 20; i++) {
				unsigned char c = fingerprint[i];
				int nT = c;//这样转是为了防止符号位扩展
				ostr << std::hex << std::setw(2) << std::setfill('0') << nT;
			}
			strLastError = ostr.str();

			//通过密码验证
			if (libssh2_userauth_password(session, username.c_str(), password.c_str())) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] Authentication by password failed ["
					<< username << "][" << password << "]" << rc << std::endl;
				strLastError = ostr.str();

				goto shutdown;
			}

			sftp_session = libssh2_sftp_init(session);

			if (!sftp_session) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] Unable to init SFTP session" << std::endl;
				strLastError = ostr.str();

				goto shutdown;
			}

			//向SFTP服务器发出新建文件请求
			sftp_handle =
				libssh2_sftp_open(sftp_session, remotepath.c_str(),
				LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
				LIBSSH2_SFTP_S_IRUSR | LIBSSH2_SFTP_S_IWUSR |
				LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH);

			if (!sftp_handle) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] Unable to open file with SFTP.  ip=" 
					<< ip <<"] remotepath=[" << remotepath << "]" << std::endl;
				strLastError = ostr.str();

				nR = -1;

				goto shutdown;
			}


			char mem[1024 * 16];
			size_t nread;
			char *ptr;
			size_t count = 0;

			do {
				nread = fread(mem, 1, sizeof(mem), local);
				if (nread <= 0) {
					//到达文件尾部
					break;
				}
				ptr = mem;
				do {
					// 向服务器写数据,直到数据写完毕
					rc = libssh2_sftp_write(sftp_handle, ptr, nread);
					if (rc < 0)
						break;
					ptr += rc; count += nread;
					nread -= rc;

					//如果设置了回调,进行回调
					if (m_bkCall)
					{
						float p = count / (float)filesize;
						m_bkCall->OnProgress(p);
					}
					//callback.end
				} while (nread);

				if ( m_isBreak.load() == true )
				{
					std::ostringstream ostr;
					ostr << "[" << __FILE__ << "][" << __LINE__ << "] 上传文件任务被用户break!" << std::endl;
					strLastError = ostr.str();

					nR = -6;
					break;
				}
			} while (rc > 0);

			libssh2_sftp_close(sftp_handle);
			libssh2_sftp_shutdown(sftp_session);

		shutdown:
			libssh2_session_disconnect(session,
				"Normal Shutdown, Thank you for playing");
			libssh2_session_free(session);

			closesocket(sock);

			fclose(local);

			return nR;//返回“0”表示成功
		}

		/*
		源码参考地址
		http://www.oschina.net/code/snippet_12_10717
		*/
		int SFTP_Libssh2::download(std::string ip, unsigned short port, std::string username, std::string password,
			std::string sftppath, std::string localpath)
		{
			unsigned long hostaddr;
			int sock, i, auth_pw = 0;
			struct sockaddr_in sin;
			const char *fingerprint;
			char *userauthlist;
			LIBSSH2_SESSION *session;
			int rc;
			LIBSSH2_SFTP *sftp_session;
			LIBSSH2_SFTP_HANDLE *sftp_handle;

			hostaddr = inet_addr(ip.c_str()); //hostaddr = htonl(0x7F000001);

			/*
			* The application code is responsible for creating the socket
			* and establishing the connection
			*/
			sock = socket(AF_INET, SOCK_STREAM, 0);

			sin.sin_family = AF_INET;
			sin.sin_port = htons(port);
			sin.sin_addr.s_addr = hostaddr;
			if (connect(sock, (struct sockaddr*)(&sin),
				sizeof(struct sockaddr_in)) != 0) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] 连接失败!" << std::endl;
				strLastError = ostr.str();
				return -1;
			}

			/* Create a session instance
			*/
			session = libssh2_session_init();

			if (!session)
				return -1;

			/* Since we have set non-blocking, tell libssh2 we are blocking */
			libssh2_session_set_blocking(session, 1);


			/* ... start it up. This will trade welcome banners, exchange keys,
			* and setup crypto, compression, and MAC layers
			*/
			rc = libssh2_session_handshake(session, sock);

			if (rc) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] 建立SSH会话失败" << rc << std::endl;
				strLastError = ostr.str();

				return -1;
			}

			/* At this point we havn't yet authenticated.  The first thing to do
			* is check the hostkey's fingerprint against our known hosts Your app
			* may have it hard coded, may go to a file, may present it to the
			* user, that's your call
			*/
			fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);

			std::ostringstream ostr;
			fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
			ostr << "Fingerprint: ";
			for (int i = 0; i < 20; i++) {
				unsigned char c = fingerprint[i];
				int nT = c;
				ostr << std::hex << std::setw(2) << std::setfill('0') << nT;
			}
			strLastError = ostr.str();

			/* check what authentication methods are available */
			userauthlist = libssh2_userauth_list(session, username.c_str(), username.length());
			if (strstr(userauthlist, "password") == NULL)
			{
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] 服务器不支持输入password方式验证!" << std::endl;
				strLastError = ostr.str();
				goto shutdown;
			}

			/* We could authenticate via password */
			if (libssh2_userauth_password(session, username.c_str(), password.c_str())) {

				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] 密码错误!" << std::endl;
				strLastError = ostr.str();
				goto shutdown;
			}

			sftp_session = libssh2_sftp_init(session);
			if (!sftp_session) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] 初始化FTL对话失败!" << std::endl;
				strLastError = ostr.str();
				goto shutdown;
			}

			/* Request a file via SFTP */
			sftp_handle =
				libssh2_sftp_open(sftp_session, sftppath.c_str(), LIBSSH2_FXF_READ, 0);


			if (!sftp_handle) {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] 打开文件失败! " << libssh2_sftp_last_error(sftp_session) << std::endl;
				strLastError = ostr.str();

				goto shutdown;
			}

			FILE *stream;
			if (fopen_s(&stream, localpath.c_str(), "wb") == 0)
			{
				do {
					char mem[1024];

					/* loop until we fail */
					rc = libssh2_sftp_read(sftp_handle, mem, sizeof(mem));

					if (rc > 0) {
						//从内存到磁盘
						fwrite(mem, 1, rc, stream);
					}
					else {
						break;
					}
				} while (1);

				fclose(stream);
			}
			else {
				std::ostringstream ostr;
				ostr << "[" << __FILE__ << "][" << __LINE__ << "] 新建本地文件失败 " << localpath << std::endl;
				strLastError = ostr.str();
			}

			libssh2_sftp_close(sftp_handle);

			libssh2_sftp_shutdown(sftp_session);

		shutdown:

			libssh2_session_disconnect(session, "Normal Shutdown, Thank you for playing");
			libssh2_session_free(session);

			closesocket(sock);//INVALID_SOCKET

			return 0;
		}
	}
}


SFTP客户端代码示例