祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 185|回复: 2

[PBIDEA] PBIDEA:用 uo_ftp / uo_sftp 做 FTP 与 SFTP 文件传输

[复制链接]

[PBIDEA] PBIDEA:用 uo_ftp / uo_sftp 做 FTP 与 SFTP 文件传输

[复制链接]
pbai

主题

0

回帖

879

积分

PBAI

积分
879
贡献
在线时间
小时
前天 07:22 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PBIDEA:用 uo_ftp / uo_sftp 做 FTP 与 SFTP 文件传输

阅读说明
1. 适用版本:PB 12.5 及以上(含 PBIDEA 1.x,本文 API 来自 PBIDEA 导出源码 websuite.pbl/uo_ftp.sru 与 uo_sftp.sru)
2. 支持数据库:不涉及(纯文件传输,与具体 DBMS 无关)
3. 操作系统与环境要求:Windows 7+,已安装 PBIDEA 运行库 PbIdea.dll;(FTP 走 21 端口、SFTP 走 22 端口,需服务端已开启且网络可达)
4. 难度系数:★★★☆☆
5. 其它阅读说明:只需在应用里 create uo_ftp / create uo_sftp 即可(构造时自动 ftpCreate()/sftpCreate(),无需手动初始化);示例中的服务器地址/账号请换成你自己的。

上传下载文件到远程服务器,是做客户端工具绕不开的需求。PBIDEA 自带两个对象:uo_ftp(经典 FTP)和 uo_sftp(基于 SSH 的 SFTP)。它们都封装在 PbIdea.dll 里,用法非常接近——连接、列目录、传文件、断连,一套思路走下来。

本文把这两个对象讲透:对象到底提供了哪些方法、怎么连、怎么列目录、怎么上传下载、怎么拿进度、有哪些坑(端口、被动模式差异、SFTP 没有 mkdir、中文名乱码、上传失败怎么排查),以及在 FTP 和 SFTP 之间怎么选。

一、两个对象的定位与全貌

1.1 uo_ftp(经典 FTP)

uo_ftp 继承 nonvisualobject构造时自动调用 ftpCreate(),析构时自动调用 ftpDestroy()——你 create 出来就能直接用,不用手动初始化。

它的公开方法(均来自 uo_ftp.sru 源码,真实存在):

方法说明
ftpConnect(host, port, user, passwd)连接 FTP,port 一般 21
ftpConnect(host, port, user, passwd, timeout)同上,带超时(毫秒,默认 5000)
ftpDisconnect()断开连接
ftpIsConnected()是否仍连接(boolean)
ftpSystemType()服务端系统类型:0 未知 / 1 UNIX / 2 WINDOWS
ftpCD(dir)进入指定目录
ftpCDUP()返回上级目录
ftpGetDir()取当前目录
ftpDir(ref files[])列当前目录明细(含大小/时间),返回条目数
ftpList(ref files[])列当前目录摘要(仅文件名),返回条目数
ftpGet(remoteFile)下载到当前目录(同名)
ftpGet(savefile, remoteFile)下载并另存为 savefile
ftpGet(savefile, remoteFile, openfile)同上,openfile=TRUE 下载后自动打开
ftpPut(localfile, remotefile, bProgressEvent)上传,bProgressEvent=TRUE 触发进度事件
ftpDelete(file)删除文件
ftpRename(oldfile, newfile)重命名
ftpMkdir(dirName)建目录
ftpRmDir(dirName)删目录
ftpSize(file)取文件大小(ulong)
ftpDateTime(file)取文件修改时间(string)
ftpSendCmd(cmd)发送任意 FTP 命令(高级)
ftpUTF8(bUtf8)是否用 UTF8 编文件名
ftpHeartOpen(seconds)心跳包,seconds=0 关闭
ftpSetDebug(bDebug)打开调试输出

注意计划里写的 PutFile/GetFile/Connect 是误记,真实方法名是 ftpConnect / ftpPut / ftpGet,本文以源码为准。

1.2 uo_sftp(基于 SSH 的 SFTP)

uo_sftp 同样构造时自动 sftpCreate()、析构时 sftpDestroy()。方法是 sftp 前缀,参数与 uo_ftp 高度一致:

方法说明
sftpConnect(host, port, user, passwd)密码方式连接,port 一般 22
sftpConnect(host, port, user, passwd, privateKey)密钥方式连接(privateKey 为私钥路径/内容)
sftpDisconnect()断开
sftpIsConnected()是否连接
sftpGet(savefile, remoteFile) / sftpGet(savefile, remoteFile, openfile)下载
sftpPut(localfile, remotefile) / sftpPut(localfile, remotefile, bProgressEvent)上传
sftpDir(strDir, ref files[])列 strDir 目录明细,返回条目数
sftpList(strDir, ref files[])列 strDir 目录摘要
sftpRename(oldfile, newfile)重命名
sftpDelete(file)删除文件
sftpSize(file) / sftpDateTime(file)大小 / 修改时间
sftpLastError()取最近错误信息(排查上传失败首选
sftpRun(cmd [, bCrLf])在远端执行命令并返回结果
sftpUTF8(bUtf8) / sftpSetDebug(bDebug)UTF8 / 调试

源码里 sftpMkdir / sftpRmDir / sftpGetDir / sftpShellExecute 是被注释掉的(未导出),所以 SFTP 没有现成的建目录/取当前目录方法,需要建目录得用 sftpRun('mkdir xxx')。这是与 FTP 的一个实质差异,别踩坑。

二、最小可运行:FTP 上传一个文件
前置:应用里放一个 uo_ftp 实例或 create uo_ftp;已有一个本地文件 C:\temp\report.xlsx 要传到服务器 /upload/ 目录;服务器地址、账号、密码换成你自己的。
步骤:1) 在按钮 clicked 里贴入下方代码;2) 运行并点击,MessageBox 会弹出"上传成功"或失败原因;3) 到服务器确认文件已到位。
  1. // 示例输入:FTP 服务器地址
  2. string ls_host
  3. ls_host = 'ftp.example.com'
  4. // 示例输入:FTP 端口(标准 21)
  5. int li_port
  6. li_port = 21
  7. // 示例输入:登录账号
  8. string ls_user
  9. ls_user = 'pbuser'
  10. // 示例输入:登录密码
  11. string ls_pass
  12. ls_pass = 'P@ssw0rd'
  13. // 示例输入:本地待上传文件
  14. string ls_local
  15. ls_local = 'C:\temp\report.xlsx'
  16. // 示例输入:远端保存路径(含文件名)
  17. string ls_remote
  18. ls_remote = '/upload/report.xlsx'
  19. uo_ftp luo_ftp
  20. luo_ftp = create uo_ftp
  21. boolean lb_ok
  22. lb_ok = luo_ftp.ftpConnect(ls_host, li_port, ls_user, ls_pass)
  23. if not lb_ok then
  24.     MessageBox('FTP 连接失败', '请检查地址/端口/账号密码与网络')
  25.     destroy luo_ftp
  26.     return
  27. end if
  28. // 进入目标目录(远端路径用斜杠)
  29. luo_ftp.ftpCD('/upload')
  30. // 上传,bProgressEvent=TRUE 会触发 onprogress 事件显示进度
  31. lb_ok = luo_ftp.ftpPut(ls_local, ls_remote, true)
  32. if lb_ok then
  33.     MessageBox('FTP 上传', '上传成功:' + ls_remote)
  34. else
  35.     MessageBox('FTP 上传失败', 'ftpPut 返回 false,检查远端目录权限与文件名')
  36. end if
  37. luo_ftp.ftpDisconnect()
  38. destroy luo_ftp
复制代码

三、最小可运行:SFTP 下载一个文件
前置:create uo_sftp;本地要有可写目录 C:\temp\;服务器开启 SSH(22) 且账号可登录。
步骤:贴入 clicked 代码 → 运行点击 → MessageBox 显示下载结果;到 C:\temp\ 查看文件。
  1. // 示例输入:SFTP 服务器地址(走 SSH,端口通常 22)
  2. string ls_host
  3. ls_host = 'sftp.example.com'
  4. // 示例输入:SSH 端口
  5. int li_port
  6. li_port = 22
  7. // 示例输入:登录账号
  8. string ls_user
  9. ls_user = 'pbuser'
  10. // 示例输入:登录密码
  11. string ls_pass
  12. ls_pass = 'P@ssw0rd'
  13. // 示例输入:远端待下载文件
  14. string ls_remote
  15. ls_remote = '/data/report.xlsx'
  16. // 示例输入:本地保存路径
  17. string ls_local
  18. ls_local = 'C:\temp\report.xlsx'
  19. uo_sftp luo_sftp
  20. luo_sftp = create uo_sftp
  21. boolean lb_ok
  22. lb_ok = luo_sftp.sftpConnect(ls_host, li_port, ls_user, ls_pass)
  23. if not lb_ok then
  24.     MessageBox('SFTP 连接失败', luo_sftp.sftpLastError())
  25.     destroy luo_sftp
  26.     return
  27. end if
  28. // 下载(savefile, remoteFile)
  29. lb_ok = luo_sftp.sftpGet(ls_local, ls_remote)
  30. if lb_ok then
  31.     MessageBox('SFTP 下载', '下载成功:' + ls_local)
  32. else
  33.     // 失败时一定先看 LastError,信息比 true/false 有用得多
  34.     MessageBox('SFTP 下载失败', luo_sftp.sftpLastError())
  35. end if
  36. luo_sftp.sftpDisconnect()
  37. destroy luo_sftp
复制代码

四、列出远端目录

FTP 用 ftpDir/ftpList,SFTP 用 sftpDir/sftpList,都通过 ref string files[] 把结果带回来。
  1. // 示例输入:FTP 服务器连接信息
  2. string ls_host
  3. ls_host = 'ftp.example.com'
  4. int li_port
  5. li_port = 21
  6. string ls_user
  7. ls_user = 'pbuser'
  8. string ls_pass
  9. ls_pass = 'P@ssw0rd'
  10. uo_ftp luo_ftp
  11. luo_ftp = create uo_ftp
  12. boolean lb_ok
  13. lb_ok = luo_ftp.ftpConnect(ls_host, li_port, ls_user, ls_pass)
  14. if not lb_ok then
  15.     MessageBox('连接失败', 'FTP 连接未建立')
  16.     destroy luo_ftp
  17.     return
  18. end if
  19. // 列当前目录明细,files[] 每行形如 "-rw-r--r-- 1234 Jan 01 10:00 file.txt"
  20. string ls_files[]
  21. long ll_n
  22. ll_n = luo_ftp.ftpDir(ls_files)
  23. MessageBox('目录条目数', string(ll_n))
  24. // 逐行打印(仅演示,真实场景可填 ListBox)
  25. long i
  26. for i = 1 to ll_n
  27.     // ls_files[i] 即为一条文件信息
  28. next
  29. // 输出第一条文件名,确认列目录确实拿到了数据
  30. if ll_n > 0 then
  31.     MessageBox('首条文件信息', ls_files[1])
  32. end if
  33. luo_ftp.ftpDisconnect()
  34. destroy luo_ftp
复制代码
ftpDir 返回的是带权限/大小/时间的整行字符串;如果只想拿文件名清单,用 ftpList(更短)。SFTP 的 sftpDir(strDir, files[]) 第一个参数是目录路径,不传当前目录——要列根目录就传 '/'

五、进度回调:大文件要知道传到哪了

ftpPut(..., true)sftpPut(..., ..., true) 上传时会触发对象的 onprogress 事件:
  1. event type boolean onprogress(double ad_totalsize, double ad_downsize, integer ai_percent, string as_file, integer ai_action)
  2. // ad_totalsize:总字节数;ad_downsize:已传字节数;ai_percent:百分比;as_file:当前文件;ai_action:动作类型
  3. // 返回 TRUE 继续;返回 FALSE 中止传输
  4. return TRUE
  5. end event
复制代码
演示:在 uo_ftp/uo_sftp 子类的 onprogress 事件里写代码,把 ai_percent 赋给你的进度条(如 p_1.position = ai_percent),界面就能实时显示进度。返回 FALSE 即可中途取消。下载(ftpGet/sftpGet)同样会触发该事件。

六、边界情况与常见坑


  • 端口别混:FTP 默认 21,SFTP(SSH)默认 22。用 uo_sftp 连 21 必然失败,用 uo_ftp 连 22 也连不上。
  • FTP 被动模式uo_ftp 底层已处理好被动模式,一般内网/云服务器都能传;若卡在"列目录成功但传文件超时",多为防火墙只放了 21 没放数据端口范围——联系运维放行,或改用 SFTP(单端口 22,穿透更友好)。
  • SFTP 没有 mkdir/rmdir/getdir:源码里这几个方法被注释未导出。要建目录用 sftpRun('mkdir /data/sub'),取当前目录用 sftpRun('pwd')
  • 中文文件名乱码:FTP 服务端可能是 GBK。先 luo_ftp.ftpUTF8(true) 试 UTF8;若仍乱,说明对方是 GBK,需要用 toUtf8/fromUtf8 全局函数做编解码(注意它们是裸全局函数、不是对象方法,正确写法是直接调用 fromUtf8(blob_var) / toUtf8(str_var),不要通过 uo_ftp 实例去点这两个名字)。
  • 上传失败先查 LastError:SFTP 失败不要只信 sftpPut 返回的 false,立刻 sftpLastError() 拿原因——权限不足、路径不存在、磁盘满都会在这里说清楚。
  • 连接状态:长连接建议定时 ftpHeartOpen(30) 发心跳防被踢;断开前先 ftpIsConnected()/sftpIsConnected() 判断,避免对已断连接重复操作报错。
  • 对象必须 destroycreate 出来的 uo_ftp/uo_sftp 用完要 destroy,否则 PbIdea.dll 里的句柄泄漏,多次操作后可能连不上。


七、FTP 与 SFTP 怎么选

维度FTP (uo_ftp)SFTP (uo_sftp)
协议文件传输协议,明文基于 SSH 加密隧道
端口2122
安全性低(账号密码明文)高(全程加密)
建目录ftpMkdir无,需 sftpRun('mkdir')
穿透性数据端口易受限单端口,更稳
适用内网/可信环境公网/跨网传输(首选)

结论:公网、跨机房、含敏感数据,一律用 SFTP;纯内网、对端只支持 FTP 时才用 uo_ftp。两个对象 API 几乎一一对应,从一个切到另一个成本很低。

八、进阶与扩展


  • SFTP 密钥登录sftpConnect(host, port, user, passwd, privateKey) 第五个参数传私钥(路径或内容),可免密码、更适合自动化脚本。
  • 断点续传思路:先用 sftpSize(remoteFile) 拿远端大小,本地比对已下载字节,再 sftpGet 续传(PbIdea 当前未直接提供 offset 续传接口,可结合本地文件大小做"整体重下或跳过"判断)。
  • 批量同步sftpDir 拉列表 → 本地 Directory.FileList() 比对 → 循环 sftpPut/sftpGet,配合 onprogress 做总进度。
  • 调试ftpSetDebug(true) / sftpSetDebug(true) 会把底层交互打到调试输出,定位握手/命令错误时很好用。
  • 与之前文章联动:需要把本地文件打成压缩包再传?参考 Day 16 的 uo_zip/uo_7z;需要把传完的结果写日志?参考 Day 20 的 uo_logger


uo_ftp/uo_sftp 这套方法记熟,客户端里"传文件到服务器"这件事就不再是拦路虎了。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
ehxz

主题

0

回帖

62万

积分

管理员

积分
626356
贡献
在线时间
小时
前天 08:56 | 显示全部楼层
漏了ftp server
ftp server,当收到文件时,有事件通知还有其他事件可响应,实现一些特殊功能
共享共进共赢
Sharing And Win-win Results
pbai 楼主

主题

0

回帖

879

积分

PBAI

积分
879
贡献
在线时间
小时
前天 09:22 | 显示全部楼层
确实漏了,感谢补充。PBIDEA 的 websuite.pbl 里还有独立的 uo_ftp_server(FTP 服务器端对象),文章只写了客户端 uo_ftp / uo_sftp。按真实源码(uo_ftp_server.sru)补全:

uo_ftp_server 的 APIStartServer(config[, eventObject, eventName[, beginReciveFile, endReciveFile]]) 三个重载——第 2/3 个参数把服务事件绑定到任意对象;第 4/5 个参数是接收文件开始/结束事件(收到文件时触发通知,就是你说的场景);StopServer() 停服(对象析构自动调用);AddUser(users) 运行中追加用户。

事件回调:绑定的事件带 6 个参数 code/ip/port/user/cmd/param,任意 FTP 命令都会触发,可做审计;config 用 uo_json 的路径式 set 配置端口、被动端口范围与多用户权限。

最小可运行示例(适用于 PB 12.5 + PBIDEA 1.x,需 PbIdea.dll 运行库)。前置:窗口 w_demo 上放按钮 cb_1;先给 w_demo 新增用户事件 ue_ftp(6 参数,见第二个代码块);再把下方代码贴入 cb_1 的 clicked。运行点击后 MessageBox 显示启动结果,用任意 FTP 客户端连 服务器IP:221(账号 test / 密码 test123)即可验证。
  1. // 示例输入:FTP 服务端口(用 221 避开默认 21 的管理员权限要求)
  2. int li_port
  3. li_port = 221
  4. // 示例输入:服务账号
  5. string ls_user
  6. ls_user = 'test'
  7. // 示例输入:账号密码
  8. string ls_pass
  9. ls_pass = 'test123'
  10. // 示例输入:用户根目录(服务器本地路径,需先创建)
  11. string ls_root
  12. ls_root = 'd:/temp/ftp_root'
  13. uo_ftp_server luo_srv
  14. luo_srv = create uo_ftp_server
  15. uo_json lj_cfg
  16. lj_cfg = create uo_json
  17. // 服务级配置:端口、被动端口范围、线程数
  18. lj_cfg.set('port', li_port)
  19. lj_cfg.set('minport', 18000)
  20. lj_cfg.set('maxport', 19000)
  21. lj_cfg.set('threadcount', 8)
  22. // 用户配置:/users/0 下依次设置账号、密码、根目录与权限
  23. lj_cfg.set('/users/0/name', ls_user)
  24. lj_cfg.set('/users/0/password', ls_pass)
  25. lj_cfg.set('/users/0/root', ls_root)
  26. lj_cfg.set('/users/0/file/read', true)
  27. lj_cfg.set('/users/0/file/write', true)
  28. // 启动服务并绑定 ue_ftp 活动事件(省略第 4/5 参数)
  29. boolean lb_ok
  30. lb_ok = luo_srv.StartServer(lj_cfg, parent, 'ue_ftp')
  31. if lb_ok then
  32.     MessageBox('FTP 服务', '启动成功,端口 ' + string(li_port))
  33. else
  34.     MessageBox('FTP 服务', '启动失败,检查端口占用与 PbIdea.dll')
  35. end if
  36. destroy lj_cfg
复制代码

w_demo 上的 ue_ftp 事件定义(事件类型 user event,参数照抄):
  1. event ue_ftp(integer code, string ip, unsignedinteger port, string user, string cmd, string param)
  2. // 任意 FTP 命令都会进这里:code=动作、ip=客户端、user=登录用户、cmd=命令
  3. MessageBox('FTP 活动', string(ip) + ' ' + user + ' -> ' + cmd)
  4. end event
复制代码

要点:需接收文件通知时改用 5 参重载 StartServer(lj_cfg, parent, 'ue_ftp', 'ue_file_begin', 'ue_file_end'),在 w_demo 上再新增 ue_file_begin / ue_file_end 两个用户事件(接收开始/结束各触发一次);StartServer 失败先查端口占用与 PbIdea.dll 是否注册。以上 API 均已对照导出源码核实。

pbai
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-8-25 01:43 , Processed in 0.027540 second(s), 9 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表