linux C++使用libssh2实现SFTP下载和FTP下载
最近写了个sftp和ftp下载的接口, 这里做个简单记录,FTP下载参考了一下网上的例子,SFTP则是用了libssh2的接口,涉及到openssl和libssh2的编译我再另一个笔记中有记录,这里贴一下链接:https://www.cnblogs.com/TssiNG-Z/p/15839297.html
关于FTP下载的参考, 链接: https://blog.csdn.net/swartz_lubel/article/details/57115820
关于FTP有一点要提及, FTP下载分为主动模式(服务器主动连接至客户端进行数据传输), 被动模式(服务器开放一个端口, 客户端新增一个连接至该端口进行数据传输), 这里我选择的下载模式是被动模式, 被动模式服务器开放的端口的计算方式为 port = port_param1 * 256 + port_param2
下面直接贴代码
1 /******************Download.h************************* 2 * 3 * brief : ftp/sftp 下载 4 * 5 * author : tsing 6 * 7 * date : 20220125 8 * 9 * version : 1.0 10 * 11 ********************************************/ 12 13 #ifndef __DOWNLOAD_ 14 #define __DOWNLOAD_ 15 16 #include <stdio.h> 17 #include <string.h> 18 #include <string> 19 #include <fstream> 20 21 using namespace std; 22 23 #include <unistd.h> 24 #include <stdlib.h> 25 #include <fcntl.h> 26 #include <sys/types.h> 27 #include <sys/time.h> 28 #include <sys/stat.h> 29 #include <sys/socket.h> 30 #include <sys/ioctl.h> 31 #include <sys/select.h> 32 #include <netinet/in.h> 33 #include <arpa/inet.h> 34 35 #include "libssh2.h" 36 #include "libssh2_publickey.h" 37 #include "libssh2_sftp.h" 38 39 #define FTPBUF 4096 40 #define FTP_CONNECT_FAIL -1 41 #define FTP_USERNAME_FAIL -2 42 #define FTP_PASSWORD_FAIL -3 43 #define FTP_PASSIVE_FAIL -4 44 #define FTP_DATALINK_FAIL -5 45 #define FTP_REQUIRE_FAIL -6 46 #define FTP_LOCALFILE_FAIL -7 47 48 #define SFTP_LIBSSH2_FAIL -1 49 #define SFTP_SESSION_FAIL -2 50 #define SFTP_HANDSHAKE_FAIL -3 51 #define SFTP_LOGINMETHOD_FAIL -4 52 #define SFTP_LOGIN_FAIL -5 53 #define SFTP_INIT_FAIL -6 54 #define SFTP_GETREMOTE_FAIL -7 55 #define SFTP_OPENFILE_FAIL -8 56 57 class CTcpClient 58 { 59 public: 60 CTcpClient(); 61 virtual ~CTcpClient(); 62 63 /* 64 * @prief 建立连接 65 * @param [in] s_ip : ip地址 66 * @param [in] i_port : 端口号 67 * @return [-1 : 失败] [0 : 成功] 68 */ 69 int to_connect(const string &s_ip, const int &i_port); 70 71 /* 72 * @prief 断开连接 73 * @return [-1 : 失败] [0 : 成功] 74 */ 75 int to_disconnect(); 76 77 /* 78 * @prief 接收数据 79 * @param [in] szbuf : 接收缓冲区 80 * @param [in] ilen : 缓冲区长度 81 * @return [-1 : 失败] [>= 0 : 接收到的长度] 82 */ 83 int to_recv(char *szbuf, const int &ilen); 84 85 /* 86 * @prief 发送数据 87 * @param [in] szsend : 待发送的内容 88 * @param [in] ilen : 发送内容的长度 89 * @return [-1 : 失败] [0 : 成功] 90 */ 91 int to_send(const char *szsend, const int &ilen); 92 93 /* 94 * @prief 交换数据 95 * @param [in] szsend : 待发送的内容 96 * @param [in] sendlen : 发送内容的长度 97 * @param [in] szbuf : 接收的内容 98 * @param [in] recvlen : 接收内容的长度 99 * @return [-1 : 失败] [0 : 成功] 100 */ 101 int to_exchange(const char *szsend, const int &sendlen, char *szbuf, const int &recvlen); 102 103 /* 104 * @prief 获取套接字描述符 105 * @return [int : 套接字] [-1 : 未连接] 106 */ 107 int get_sockfd(); 108 109 private: 110 int _m_sockfd; 111 bool _m_isconnect; 112 }; 113 114 int ftp_getfile(const string &ip, const int &port, const string &username, const string &pwd, const string &remote_file, const string &local_file, char *szMsg); 115 116 int sftp_getfile(const string &ip, const int &port, const string &username, const string &pwd, const string &remote_file, const string &local_file, char *szMsg); 117 118 #endif 119 120 /*****************************************Download.cpp**************************************************/ 121 122 #include "Download.h" 123 bool str_replace(string &s, const string &symbol, const string &replace) 124 { 125 if (s.empty() || symbol.empty()) return false; 126 127 for (size_t pos = s.find(symbol); pos != string::npos; pos = s.find(symbol, pos)) 128 { 129 if (replace.empty()) 130 { 131 s.erase(pos, symbol.length()); 132 } 133 else 134 { 135 s.replace(pos, symbol.length(), replace); 136 } 137 } 138 return true; 139 } 140 141 bool ftp2link(const string &ftp_msg, string &ip, int &port) 142 { 143 if (ftp_msg.empty()) return false; 144 145 size_t start_pos = ftp_msg.find("("); 146 size_t end_pos = ftp_msg.find(")"); 147 string link_info = ftp_msg.substr((start_pos + 1), (end_pos - start_pos - 1)); 148 149 str_replace(link_info, ",", "."); 150 start_pos = -1; 151 for (int i = 0; i < 4; i++) 152 { 153 start_pos = link_info.find(".", start_pos + 1); 154 } 155 156 ip = link_info.substr(0, start_pos); 157 string port0 = link_info.substr(start_pos + 1, link_info.rfind(".") - start_pos - 1); 158 string port1 = link_info.substr(link_info.rfind(".") + 1, string::npos); 159 port = (atoi(port0.c_str()) * 256 + atoi(port1.c_str())); 160 161 printf("ip[%s] port[%d] port0[%s] port1[%s]\n", ip.c_str(), port, port0.c_str(), port1.c_str()); 162 return true; 163 } 164 165 CTcpClient::CTcpClient() 166 { 167 _m_sockfd = socket(AF_INET, SOCK_STREAM, 0); 168 } 169 170 CTcpClient::~CTcpClient() 171 { 172 if (_m_isconnect) 173 { 174 close(_m_sockfd); 175 } 176 } 177 178 int CTcpClient::to_connect(const string &s_ip, const int &i_port) 179 { 180 if (_m_sockfd == -1) 181 { 182 _m_sockfd = socket(AF_INET, SOCK_STREAM, 0); 183 if (_m_sockfd == -1) return -1; 184 } 185 186 ioctl(_m_sockfd, FIONBIO, 1); 187 188 sockaddr_in addr_info; 189 memset(&addr_info, 0, sizeof(sockaddr_in)); 190 191 addr_info.sin_family = AF_INET; 192 addr_info.sin_addr.s_addr = inet_addr(s_ip.c_str()); 193 addr_info.sin_port = htons(i_port); 194 195 int ret_val = connect(_m_sockfd, (sockaddr*)(&addr_info), sizeof(sockaddr)); 196 if (ret_val == -1) 197 { 198 timeval timeout; 199 timeout.tv_sec = 10; 200 timeout.tv_usec = 0; 201 fd_set readable_set; 202 FD_ZERO(&readable_set); 203 FD_SET(_m_sockfd, &readable_set); 204 205 int errno = 0; 206 int len = sizeof(int); 207 if (0 < select(_m_sockfd + 1, 0, &readable_set, 0, &timeout)) 208 { 209 getsockopt(_m_sockfd, SOL_SOCKET, SO_ERROR, &errno, (socklen_t*)(&len)); 210 if (errno == 0) 211 { 212 ioctl(_m_sockfd, FIONBIO, 0); 213 _m_isconnect = true; 214 return 0; 215 } 216 } 217 218 _m_isconnect = false; 219 return -1; 220 } 221 222 ioctl(_m_sockfd, FIONBIO, 0); 223 _m_isconnect = true; 224 return 0; 225 } 226 227 int CTcpClient::to_disconnect() 228 { 229 if (_m_isconnect) 230 { 231 close(_m_sockfd); 232 return 0; 233 } 234 return -1; 235 } 236 237 int CTcpClient::to_recv(char *szbuf, const int &ilen) 238 { 239 if (_m_sockfd == -1) return -1; 240 241 //select超时时间 242 timeval timeout; 243 timeout.tv_sec = 1; 244 timeout.tv_usec = 0; 245 246 //可读事件fd集合 247 fd_set readable_list; 248 FD_ZERO(&readable_list); 249 FD_SET(_m_sockfd, &readable_list); 250 251 if (0 < select(_m_sockfd + 1, &readable_list, 0, 0, &timeout)) 252 { 253 return (recv(_m_sockfd, szbuf, ilen, 0)); 254 } 255 256 return -1; 257 } 258 259 int CTcpClient::to_send(const char *szsend, const int &ilen) 260 { 261 if (_m_sockfd == -1) return -1; 262 263 timeval timeout; 264 timeout.tv_sec = 1; 265 timeout.tv_usec = 0; 266 267 fd_set writable_list; 268 FD_ZERO(&writable_list); 269 FD_SET(_m_sockfd, &writable_list); 270 271 if (0 < select(_m_sockfd + 1, 0, &writable_list, 0, &timeout)) 272 { 273 int i_already_send = 0; 274 int i_send_cnt = 0; 275 while (i_already_send != ilen) 276 { 277 i_send_cnt = send(_m_sockfd, szsend, ilen, 0); 278 if (i_send_cnt == -1) return -1; 279 280 i_already_send += i_send_cnt; 281 } 282 283 return 0; 284 } 285 286 return -1; 287 } 288 289 int CTcpClient::to_exchange(const char *szsend, const int &sendlen, char *szbuf, const int &recvlen) 290 { 291 int ret = 0; 292 293 ret = to_send(szsend, sendlen); 294 if (ret == -1) 295 { 296 printf("tcp exchange send failure\n"); 297 return -1; 298 } 299 300 ret = to_recv(szbuf, recvlen); 301 if (ret > 0) 302 { 303 printf("tcp exchange with response : %s\n", szbuf); 304 } 305 else 306 { 307 printf("tcp exchange with no response\n"); 308 return -1; 309 } 310 return 0; 311 } 312 313 int CTcpClient::get_sockfd() 314 { 315 if (_m_isconnect) 316 { 317 return _m_sockfd; 318 } 319 320 return -1; 321 } 322 323 int ftp_getfile(const string &ip, const int &port, const string &username, const string &pwd, const string &remote_file, const string &local_file, char *szMsg) 324 { 325 int ftp_ret = 0 ; //接口返回值 326 char ftp_recv[FTPBUF] = { 0 }; //FTP接收缓存 327 string ftp_cmd = "" ; //FTP控制指令 328 CTcpClient cmd_link; //指令连接,用于传输控制消息 329 CTcpClient data_link; //数据连接,用于传输数据 330 331 //打开tcp通道 332 ftp_ret = cmd_link.to_connect(ip, port); 333 if (ftp_ret == -1) 334 { 335 sprintf(szMsg, "ftp connect fail..."); 336 return FTP_CONNECT_FAIL; 337 } 338 339 //接收登录消息 340 ftp_ret = cmd_link.to_recv(ftp_recv, FTPBUF); 341 if (ftp_ret > 0) 342 { 343 printf("ftp sock open success with response : %s\n", ftp_recv); 344 } 345 else 346 { 347 printf("ftp sock open success with no response\n"); 348 } 349 350 //发送登录用户名 351 ftp_cmd = "USER "; 352 ftp_cmd += (username + "\n"); 353 memset(ftp_recv, 0, FTPBUF); 354 if (0 != cmd_link.to_exchange(ftp_cmd.c_str(), ftp_cmd.length(), ftp_recv, FTPBUF)) 355 { 356 sprintf(szMsg, "ftp send username [%s] fail [%s]", username.c_str(), ftp_recv); 357 return FTP_USERNAME_FAIL; 358 } 359 360 //发送登录密码 361 ftp_cmd = "PASS "; 362 ftp_cmd += (pwd + "\n"); 363 memset(ftp_recv, 0, FTPBUF); 364 if (0 != cmd_link.to_exchange(ftp_cmd.c_str(), ftp_cmd.length(), ftp_recv, FTPBUF)) 365 { 366 sprintf(szMsg, "ftp send password [%s/%s] fail [%s]", username.c_str(), pwd.c_str(), ftp_recv); 367 return FTP_PASSWORD_FAIL; 368 } 369 370 //设置为被动模式 371 ftp_cmd = "PASV\n"; 372 memset(ftp_recv, 0, FTPBUF); 373 if (0 != cmd_link.to_exchange(ftp_cmd.c_str(), ftp_cmd.length(), ftp_recv, FTPBUF)) 374 { 375 sprintf(szMsg, "ftp set fassive fail [%s]", ftp_recv); 376 return FTP_PASSIVE_FAIL; 377 } 378 379 string datalink_ip = ""; 380 int datalink_port = 0 ; 381 382 //解析数据连接基本信息 383 ftp2link(ftp_recv, datalink_ip, datalink_port); 384 385 //打开数据连接 386 ftp_ret = data_link.to_connect(datalink_ip, datalink_port); 387 if (ftp_ret == -1) 388 { 389 sprintf(szMsg, "ftp data link open fail"); 390 return FTP_DATALINK_FAIL; 391 } 392 393 //发送下载请求 394 ftp_cmd = "RETR "; 395 ftp_cmd += (remote_file + "\n"); 396 memset(ftp_recv, 0, FTPBUF); 397 if (0 != cmd_link.to_exchange(ftp_cmd.c_str(), ftp_cmd.length(), ftp_recv, FTPBUF)) 398 { 399 sprintf(szMsg, "ftp require file [%s] fail [%s]", remote_file.c_str(), ftp_recv); 400 return FTP_REQUIRE_FAIL; 401 } 402 403 //打开本地文件 404 FILE *fp = fopen(local_file.c_str(), "wb+"); 405 if (fp != NULL) 406 { 407 int recv_len = 0; 408 char mem[40960] = { 0 }; 409 410 //将数据连接中的数据写入文件中 411 while (0 < (recv_len = data_link.to_recv(mem, sizeof(mem)))) 412 { 413 fwrite(mem, 1, recv_len, fp); 414 fflush(fp); 415 memset(mem, 0, sizeof(mem)); 416 } 417 418 fclose(fp); 419 } 420 else 421 { 422 sprintf(szMsg, "ftp create local file [%s] fail", local_file.c_str()); 423 return FTP_LOCALFILE_FAIL; 424 } 425 426 ftp_cmd = "QUIT\n"; 427 memset(ftp_recv, 0, FTPBUF); 428 429 cmd_link.to_exchange(ftp_cmd.c_str(), ftp_cmd.length(), ftp_recv, FTPBUF); 430 sprintf(szMsg, "ftp download success [%s]", ftp_recv); 431 return 0; 432 } 433 434 435 int sftp_getfile(const string &ip, const int &port, const string &username, const string &pwd, const string &remote_file, const string &local_file, char *szMsg) 436 { 437 int i = 0, auth_pw = 0; 438 const char *fingerprint; 439 char *userauthlist; 440 LIBSSH2_SESSION *session = NULL; 441 LIBSSH2_SFTP *sftp_session = NULL; 442 LIBSSH2_SFTP_HANDLE *sftp_handle = NULL; 443 FILE *fp = NULL; 444 CTcpClient tcplink; 445 446 //libssh2初始化 447 int rc = libssh2_init (0); 448 if (rc != 0) { 449 sprintf(szMsg, "libssh2 init fail"); 450 return SFTP_LIBSSH2_FAIL; 451 } 452 453 //建立tcp连接 454 tcplink.to_connect(ip, port); 455 456 //建立ssh通讯session 457 session = libssh2_session_init(); 458 if(!session) 459 { 460 sprintf(szMsg, "libssh2 start session fail"); 461 return SFTP_SESSION_FAIL; 462 } 463 464 //阻塞通讯模式 465 libssh2_session_set_blocking(session, 1); 466 467 //ssh交换密钥等操作 468 rc = libssh2_session_handshake(session, tcplink.get_sockfd()); 469 if(rc) 470 { 471 sprintf(szMsg, "libssh2 session handshake fail"); 472 return SFTP_HANDSHAKE_FAIL; 473 } 474 475 //密钥认证 476 fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); 477 478 //检查服务器可用的登录方式 479 userauthlist = libssh2_userauth_list(session, username.c_str(), username.length()); 480 if (strstr(userauthlist, "password") != NULL) 481 { 482 printf("libssh2 server with support login method [%s]\n", userauthlist); 483 } 484 else 485 { 486 sprintf(szMsg, "libssh2 server not support username/password login"); 487 return SFTP_LOGINMETHOD_FAIL; 488 } 489 490 491 //密码登录 492 if (libssh2_userauth_password(session, username.c_str(), pwd.c_str())) 493 { 494 sprintf(szMsg, "libssh2 login fail with username[%s] password[%s]", username.c_str(), pwd.c_str()); 495 return SFTP_LOGIN_FAIL; 496 } 497 498 sftp_session = libssh2_sftp_init(session); 499 if (!sftp_session) 500 { 501 sprintf(szMsg, "libssh2 sftp init fail"); 502 return SFTP_INIT_FAIL; 503 } 504 505 sftp_handle = libssh2_sftp_open(sftp_session, remote_file.c_str(), LIBSSH2_FXF_READ, 0); 506 if (!sftp_handle) 507 { 508 sprintf(szMsg, "libssh2 remote file open fail [%s]", remote_file.c_str()); 509 return SFTP_GETREMOTE_FAIL; 510 } 511 512 fp = fopen(local_file.c_str(),"wb+"); 513 if(fp == NULL) 514 { 515 sprintf(szMsg, "local file open fail [%s]", local_file.c_str()); 516 return SFTP_OPENFILE_FAIL; 517 } 518 519 char mem[40960] = { 0 }; 520 while (0 < (rc = libssh2_sftp_read(sftp_handle, mem, sizeof(mem)))) 521 { 522 fwrite(mem, rc, 1, fp); 523 memset(mem, 0, sizeof(mem)); 524 } 525 fclose(fp); 526 527 if (sftp_handle) 528 libssh2_sftp_close(sftp_handle); 529 if (sftp_session) 530 libssh2_sftp_shutdown(sftp_session); 531 532 if (session) 533 { 534 libssh2_session_disconnect(session, "Normal Shutdown, Thank you for playing"); 535 libssh2_session_free(session); 536 } 537 538 libssh2_exit(); 539 tcplink.to_disconnect(); 540 return 0; 541 }
以上, 如有错误疏漏等, 欢迎讨论指点, 感谢.