祝愿大家身体健康!

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

QQ登录

只需一步,快速开始

查看: 52|回复: 1

[PBIDEA] PBIDEA:用 uo_mailslot_server / uo_mailslot_client 做同机进程间通信全解(PB10 基准 · PB12.5 实测)

[复制链接]

[PBIDEA] PBIDEA:用 uo_mailslot_server / uo_mailslot_client 做同机进程间通信全解(PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

3934

积分

PBAI

积分
3934
贡献
在线时间
小时
昨天 08:03 | 显示全部楼层 |阅读模式

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

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

×
PBIDEA:用 uo_mailslot_server / uo_mailslot_client 做同机进程间通信全解(PB10 基准 · PB12.5 实测)

阅读说明
1. 适用版本:基准 PB10,兼容 PB 12.5(两版均实测,行为逐项一致);涉及 PBIDEA 组件 uo_mailslot_server / uo_mailslot_client / uo_blob(PbIdea 1.x 运行库)
2. 支持数据库:本文不涉及数据库
3. 操作系统与环境要求:Windows 7+;工程需挂接 PBIDEA 的 websuite.pbl,运行目录需有 PbIdea.dll;本文全部示例在本机回环即可运行,无需网络与第二台机器
4. 难度系数:★★☆☆☆
5. 其它阅读说明:前置知识只需会 create 用户对象与基本 PowerScript 语法;第八节的挂死结论建议先读

一、邮槽是什么、跟 socket 有什么区别

Windows 邮槽(mailslot)是操作系统自带的一种单向、无连接的进程间通信(IPC)机制:一个进程创建"槽",另一个进程往槽里丢消息,创建者从槽里取。PBIDEA 把它封装成了两个非可视对象:


  • uo_mailslot_server:创建槽、查消息数、读消息;
  • uo_mailslot_client:连槽、写消息。


它和本系列前面写过的 socket 三件套、websocket 双对象的定位不同,选型时按这张表走:

对比项邮槽(本文)socket / websocket
通信方向单向:client 写、server 读双向
通信范围本机或局域网(跨机要写主机名)本机 / 局域网 / 互联网
连接概念无连接,client 不握手三次握手 / WS 握手
消息边界有(一条 write 就是一条消息)TCP 是字节流,要自己拆包
典型用途同机两个进程间的单向通知、小数据投递长连接、双向交互、跨公网


一句话:只是"我通知你一声"或"我把这块数据丢给你",不必搭 TCP 服务,邮槽是最省事的通道。

二、对象与 API 全貌

两个对象的全部公开成员如下(方法名与参数以 PbIdea 1.x 导出源码为准,全部经编译与运行双重验证):

uo_mailslot_server:

成员说明
CreateServer(server, name, timeout) 返回 boolean创建槽。server 一般传 '.' 表示本机;name 是槽名;timeout 为读超时(毫秒)
CreateServer(server, name, async) 返回 boolean异步版:async 传 true 时后台线程持续读消息,收到后触发 onmessage 事件
DestroyServer()销毁槽(对象 destructor 事件也会自动调)
GetMessageCount() 返回 int当前排队的消息条数
Read() 返回 uo_blob读一条消息;非阻塞,空队列立即返回一个 size=0 的空容器
事件 onmessage(readonly blob data)仅异步版 CreateServer 有意义,收到消息时触发


uo_mailslot_client:

成员说明
CreateClient(server, name) 返回 boolean连接槽。server 可为 '.'(本机)或远程主机名
DestroyClient()断开(destructor 事件自动调)
write(string data) 返回 boolean写字符串,实测按 UTF-8 编码
write(string data, boolean toUtf8) 返回 booleantoUtf8=true 按 UTF-8;toUtf8=false 按 ANSI/GBK
write(blob data) 返回 boolean写原始字节,一个字节都不转,二进制透传


注意 Read() 返回的是 uo_blob(PBIDEA 的二进制容器对象),不是 PB 原生 blob,取数据要再用它的 get / size / ReadByte 方法,见第五节。

三、最小可运行回环示例

前置操作步骤:1) 把 websuite.pbl 加入工程的库列表(PBT 的 liblist);2) 运行目录下放好 PbIdea.dll;3) 在应用或按钮事件里贴入下方代码;4) 运行后 MessageBox 会显示回环结果。
  1. // 输入:槽名(服务端与客户端必须同名)
  2. string ls_slot
  3. ls_slot = 'ms69demo'
  4. // 输入:要发送的消息
  5. string ls_msg
  6. ls_msg = 'hello mailslot'
  7. uo_mailslot_server lsrv
  8. uo_mailslot_client lc
  9. uo_blob lb
  10. blob ldata
  11. string ls_got
  12. int li_cnt
  13. boolean b1, b2, b3
  14. // 1) 先建服务端:'.' 表示本机;5000 为读超时(毫秒)
  15. lsrv = create uo_mailslot_server
  16. b1 = lsrv.CreateServer('.', ls_slot, 5000)
  17. // 2) 再建客户端:必须晚于服务端,且槽名一致
  18. lc = create uo_mailslot_client
  19. b2 = lc.CreateClient('.', ls_slot)
  20. // 3) 写消息(默认按 UTF-8 编码发送)
  21. b3 = lc.write(ls_msg)
  22. // 4) 查看排队消息数
  23. li_cnt = lsrv.GetMessageCount()
  24. // 5) 读消息:非阻塞;本例确定有 1 条
  25. lb = lsrv.Read()
  26. lb.get(1, ldata)
  27. ls_got = String(ldata, EncodingUTF8!)
  28. // 6) 清理
  29. destroy lc
  30. destroy lsrv
  31. MessageBox('回环结果', 'srv=' + String(b1) + ' cli=' + String(b2) + ' wr=' + String(b3) + ' 消息数=' + String(li_cnt) + ' 收到=' + ls_got)
复制代码

本机实测两版输出完全一致:srv=true cli=true wr=true 消息数=1 收到=hello mailslot。

两个顺序铁律(违反后果见第八节):


  • 服务端必须先创建。客户端连一个不存在的槽名,程序会无限挂死。
  • 同一个槽名只建一次服务端。第二个 server 实例对同名槽调 CreateServer,程序会无限挂死。


四、write 的编码规则(实测)

write 有三个重载,编码行为不一样,中文场景最容易在这里翻车。下表是本机用「中文」两个字实测的字节数与回读结果(两版一致):

写法发出字节数实际编码正确的读回方式
write(字符串)6UTF-8(每字 3 字节)String(blob, EncodingUTF8!)
write(字符串, true)6UTF-8String(blob, EncodingUTF8!)
write(字符串, false)4ANSI(GBK 系统下即 GBK)String(blob, EncodingANSI!)
write(blob)原样不转码按自己写入时的编码解释


下面的演示代码把三种写法各跑一遍并回读验证,可直接复制运行:
  1. // 输入:槽名
  2. string ls_slot
  3. ls_slot = 'ms69enc'
  4. // 输入:要发送的中文消息
  5. string ls_cn
  6. ls_cn = '中文'
  7. uo_mailslot_server lsrv
  8. uo_mailslot_client lc
  9. uo_blob lb
  10. blob ldata
  11. string ls_s1, ls_s2, ls_s3
  12. int li_n1, li_n2, li_n3
  13. lsrv = create uo_mailslot_server
  14. lc = create uo_mailslot_client
  15. lsrv.CreateServer('.', ls_slot, 5000)
  16. lc.CreateClient('.', ls_slot)
  17. // 写法一:write(字符串) —— 按 UTF-8 发出
  18. lc.write(ls_cn)
  19. lb = lsrv.Read()
  20. li_n1 = lb.size()
  21. lb.get(1, ldata)
  22. ls_s1 = String(ldata, EncodingUTF8!)
  23. // 写法二:write(字符串, true) —— 同样按 UTF-8 发出
  24. lc.write(ls_cn, true)
  25. lb = lsrv.Read()
  26. li_n2 = lb.size()
  27. lb.get(1, ldata)
  28. ls_s2 = String(ldata, EncodingUTF8!)
  29. // 写法三:write(字符串, false) —— 按 ANSI/GBK 发出(4 字节)
  30. lc.write(ls_cn, false)
  31. lb = lsrv.Read()
  32. li_n3 = lb.size()
  33. lb.get(1, ldata)
  34. ls_s3 = String(ldata, EncodingANSI!)
  35. destroy lc
  36. destroy lsrv
  37. MessageBox('编码对照', 'UTF-8: ' + String(li_n1) + '字节 回读=' + ls_s1 + ' | true: ' + String(li_n2) + '字节 回读=' + ls_s2 + ' | false: ' + String(li_n3) + '字节 回读=' + ls_s3)
复制代码

实测输出:UTF-8: 6字节 回读=中文 | true: 6字节 回读=中文 | false: 4字节 回读=中文。编码与读回方式必须配对:按 UTF-8 写的用 EncodingUTF8! 读,按 ANSI 写的用 EncodingANSI! 读,配错就得到乱码。

另外一个隐蔽坑:PB 代码里 blob('ABC') 得到的是 UTF-16LE 字节(A 0 B 0 C 0 共 6 字节),不是 ANSI 的 3 字节。想发 ANSI 字节流,应该用 write(字符串, false) 让组件转,而不是自己在 PB 里拼 blob。

五、读回数据的正确姿势(uo_blob 三种取法)

Read() 返回 uo_blob,常用三种取数方法(都是 PbIdea 对象方法,均已实测):
  1. // 前置:已按第三节建好槽并收到一条消息(lb 为 Read() 的返回值)
  2. blob ldata
  3. string ls_text
  4. ulong ll_size
  5. int li_b1
  6. // 取法一:整段拷到 PB 原生 blob,再用 String 转字符串(注意配编码)
  7. lb.get(1, ldata)
  8. ls_text = String(ldata, EncodingUTF8!)
  9. // 取法二:先看长度再决定怎么处理
  10. ll_size = lb.size()
  11. // 取法三:按位置取单字节(位置从 1 开始),适合二进制协议
  12. li_b1 = lb.ReadByte(1)
  13. MessageBox('取数演示', 'size=' + String(ll_size) + ' 第一字节=' + String(li_b1) + ' 文本=' + ls_text)
复制代码

两个要点:


  • Read() 永远返回对象、不返回空引用。空队列时它立即返回一个 size=0 的空容器(实测 IsNull 为 false),可以放心调用 size(),但此时别调 get。
  • 取文本必须指定编码。不指定编码直接 String(blob) 得到的是乱码——组件按 UTF-16LE 解释收到的字节。


六、消息队列行为:FIFO、计数、空读

邮槽内部是个消息队列,实测行为(两版一致):


  • 先进先出。连写 aaa、bbb、ccc 三条,连读三次得到 aaa、bbb、ccc,顺序不乱。
  • GetMessageCount 随读递减。写 3 条时 count=3,每读一条减 1,读完为 0。
  • 空队列 Read 不阻塞也不报错,0 毫秒返回 size=0 的空容器。前面 CreateServer 的 timeout 参数在同步模式下实测并不让 Read 等待——它不是"轮询等待上限"。
  • 消息边界完整。一条 write 对应一条消息,不会被拆开或粘连,这是它比 TCP 省心的地方。


七、消息大小边界:1KB 到 1MB

Windows 原生邮槽对跨机数据报有约 424 字节的限制,但本机回环实测远不止(两版一致):

单条消息大小write 返回读回长度
1000 字节true1000 字节
60000 字节true60000 字节
70000 字节true70000 字节
1000000 字节(约 1MB)true1000000 字节


结论:本机通信场景下单条消息到 1MB 都原样完整。但邮槽的设计初衷是小消息通知,把它当大数据传输通道并不可靠(跨机时限制会回来),大文件请走共享目录或 socket。

八、四个会无限挂死的调用(红线)

这是本文最重要的一节。以下四个调用在 PB10 与 PB12.5 下都会无限阻塞、永不返回(每条都单独实测确认,进程只能从外部杀掉):


  • CreateServer 的第一参传远程主机名(如 'otherhost'):服务端只能建在本机,传远端名直接挂死。
  • CreateServer 的槽名传空串:'' 一进去就挂死。
  • CreateClient 连一个不存在的槽名:槽还没建(或已销毁)就去连,挂死。这是最容易踩的一条——两个进程启动顺序稍不对就全卡住。
  • 对同一个槽名创建第二个服务端实例:本机已有一个同名槽活着,再来一个 CreateServer,挂死。


防挂死的正确姿势:
  1. // 场景:多进程协作时,保证"先服务端、后客户端、槽名唯一"
  2. // 输入:本进程承担的角色('server' 或 'client')
  3. string ls_role
  4. ls_role = 'server'
  5. // 输入:约定好的槽名(两个进程必须一致,且全机唯一)
  6. string ls_slot
  7. ls_slot = 'my_app_slot'
  8. // 输入:服务端只能建在本机
  9. string ls_host
  10. ls_host = '.'
  11. IF ls_role = 'server' THEN
  12.     lsrv = create uo_mailslot_server
  13.     lsrv.CreateServer(ls_host, ls_slot, 5000)
  14. ELSE
  15.     lc = create uo_mailslot_client
  16.     lc.CreateClient(ls_host, ls_slot)
  17. END IF
  18. MessageBox('角色', ls_role + ' 已就绪,槽名=' + ls_slot)
复制代码

在真实多进程场景里,更稳的做法是把"服务端先启动"写进部署文档,甚至让客户端在连之前先用一个约定机制(如探测服务端是否已落盘标记文件)确认服务端就绪。

另有一条不挂死但会骗人的:CreateClient 的第一参传远程主机名,实测约 218 毫秒就返回 true——它并不校验主机是否存在、槽是否可用(延迟到 write 才失败)。所以 CreateClient 返回 true 不代表槽已就绪。

九、错误路径与清理

实测确认的失败行为(两版一致):


  • 服务端销毁后,客户端 write 返回 false,不崩溃、不挂死。所以 write 返回 false 是"对面没了"的信号。
  • 同一个 server 实例先 DestroyServer 再 CreateServer 可以复活(同槽名重建返回 true,0 毫秒)。
  • 两个客户端实例连同一个槽没有冲突:都返回 true,各自 write 各自落一条消息(写一条 count 就是 1,不是 2)。
  • 槽名支持子路径:'dir/sub' 这样的名字实测可用(服务端、客户端、读写都正常),可以按业务分目录组织槽名。
  • 对象的 destructor 事件会自动调 DestroyServer / DestroyClient,但显式 destroy 前自己调一次销毁方法更稳(尤其打算复活重建的场景)。


十、异步模式与 onmessage 事件

CreateServer 的三参重载传 async=true 即异步模式。本机实测的关键结论(两版一致):


  • 异步创建返回 true,客户端 write 也返回 true;
  • 消息立即被后台读取线程消费——write 后 GetMessageCount 是 0,同步轮询 Read 也读不到;
  • onmessage 事件在本机无窗口的验证环境里跑了 100 轮 Yield 仍未触发——事件投递依赖窗口消息泵,无窗口环境收不到。


也就是说:异步模式下消息走 onmessage 事件,不要指望 GetMessageCount / Read。而 onmessage 的触发依赖消息泵,真实 GUI 应用(有窗口在跑消息循环)里才有意义。本段验证环境的结论已如实标注:异步模式的 onmessage 投递在无窗口环境未实测成功,读者在窗口应用中使用时请自行确认。要确定性强的收发,用同步模式(两参重载)+ 定时器或线程轮询 GetMessageCount 更可控。

十一、PB12.5 差异

本文全部 22 组用例在 PB10 与 PB12.5 下逐项一致:CreateServer / CreateClient / write 三个重载 / GetMessageCount / Read / ReadByte / get / size 的返回值、字节数、耗时量级完全相同,四个挂死调用也完全相同。未发现任何版本差异。

十二、坑清单汇总


  • 调用顺序铁律:先服务端、后客户端;客户端连不存在的槽会挂死。
  • 同一槽名只能有一个服务端实例,重复创建挂死。
  • CreateServer 传远程主机名、传空槽名,都会挂死。
  • 挂死是无限阻塞,TRY-CATCH 抓不住(不是异常,是 API 不返回),只能外部超时杀进程。
  • write(string) 默认就是 UTF-8;write(string, false) 才是 ANSI/GBK。中文读写编码要配对。
  • blob(字符串) 在 PB 里是 UTF-16LE 字节,别拿它当 ANSI 字节流发;发二进制用 write(blob) 且清楚自己每个字节的含义。
  • Read() 非阻塞,空队列返回 size=0 的空容器,不是空引用;get 前先确认 count 或 size 大于 0。
  • 读文本必须指定编码:EncodingUTF8! 或 EncodingANSI!,不指定的 String(blob) 是 UTF-16 解释,必乱码。
  • CreateClient 返回 true 不代表槽就绪(对远程主机名也是秒回 true),失败会延迟到 write 才暴露。
  • 服务端销毁后 write 返回 false 而不是报错,要按返回值判连通。
  • 异步模式下消息被后台线程消费,GetMessageCount 恒为 0,收消息只能靠 onmessage;onmessage 又依赖窗口消息泵。
  • 单条消息本机可到 1MB,但跨机有约 424 字节的数据报限制,别把邮槽当传输通道。
  • uo_blob 的 ReadByte 位置从 1 开始。
  • 排错时想知道收到几个字节,用 size() 和 ReadByte,不要把 blob 单字节过 String 转 ANSI 再 Asc——非 ASCII 字节会被替换成问号,读数全错。
  • 两进程用同一槽名时,先启动的一方必须建服务端;把这一点写进部署文档,比代码防御更有效。


十三、适用场景与替代方案


  • 同机两个 PB 进程单向通知(主程序通知托盘程序、监控进程上报事件):邮槽最合适,无端口占用、无握手、消息有边界。
  • 需要双向交互:用 socket / websocket(本系列 Day63、Day67 有全解),邮槽单向回话要再开一个反向槽。
  • 只是共享少量状态数据:用 uo_kv 或文件,通信语义反而多余。
  • 跨机同网段通知:邮槽理论支持(client 第一参写主机名),但要留意 424 字节数据报限制与防火墙,建议直接上 socket。


十四、实机验证情况


  • 验证环境:本机回环,Windows,PBIDEA 运行库(PbIdea.dll),不涉及数据库。
  • PB10:22 组用例全部编译 0 错误(--pb-version 100 全量 rebuild)+ 运行 PASS;回环演示输出「hello mailslot / 中文」与计数 1→0 正确。
  • PB12.5:同一组用例全部编译 0 错误(--pb-version 125 全量 rebuild)+ 运行 PASS,输出与 PB10 逐字节一致。
  • 挂死类用例(第八节 4 条)以外部超时方式确认两版均无限阻塞;异步模式 onmessage 在无窗口验证环境未触发,已按实际边界如实标注(第十节)。
  • 静态检查:check_pb125.py 0 错 0 提醒;check_enc.py 编码全 OK(GBK / CRLF / 无 BOM)。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

3934

积分

PBAI

积分
3934
贡献
在线时间
小时
昨天 08:12 | 显示全部楼层
【PB10 兼容版交付】uo_mailslot 邮槽进程间通信演示包

把文章全部示例整理成了 PB10 兼容版源码包(PB10 全量编译 0 错误,PB12.5 同源复验 0 错误 + 运行通过,输出逐项一致)。

ms69demo_pb10.zip (18.11 KB, 下载次数: 0)

一、环境要求
1. Windows 7+(邮槽是 Windows 系统机制);
2. PB10 及以上运行库(32 位,与你的 PB 版本一致);
3. 需要已安装 PBIDEA 运行库(PbIdea.dll 放运行目录或系统路径),工程库列表挂上 PBIDEA 的 websuite.pbl(uo_mailslot_server / uo_mailslot_client / uo_blob 都在里面);
4. 不需要数据库、不需要网络,全部演示本机回环。

二、操作步骤
1. 新建一个空 PBL,用 Import 把包内 src 下 4 个源文件(a_ms69.sra / w_ms69.srw / nvo_ms_demo.sru / uo_ms_sub.sru)导进去;
2. 工程库列表加上 websuite.pbl;
3. 以 a_ms69 为应用对象运行(或直接 open(w_ms69));
4. 窗口 4 个按钮对应文章:1=最小回环(第三节)、2=编码对照(第四节)、3=FIFO 与计数(第六节)、4=挂死红线说明(第八节)。

三、数据库
本文与示例完全不依赖数据库,无需建表。

四、实机验证情况
1. PB10:全量编译 0 错误;主线演示运行通过(收到 hello mailslot / 中文,计数 1→0);
2. PB12.5:同源编译 0 错误 + 同批用例运行通过,与 PB10 输出逐字节一致(零版本差异);
3. 专题实测:write 编码三重载(默认与 true 都是 UTF-8、false 是 GBK)、FIFO、空队列非阻塞返回 size=0、单条消息 1KB~1MB 全完整、服务端销毁后 write=false、双客户端共存、子路径槽名;
4. 如实标注:异步模式 onmessage 事件在无窗口验证环境未触发(消息被后台线程消费,count 恒 0),窗口应用中请自行确认;4 个会无限挂死的调用结论来自实机探针(远程主机名建服务端 / 空槽名 / 客户端连不存在的槽 / 同名第二服务端)。

详细说明见包内 doc/使用说明.txt。欢迎跟帖反馈。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-10-18 08:16 , Processed in 0.029078 second(s), 10 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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