祝愿大家身体健康!

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

QQ登录

只需一步,快速开始

查看: 34|回复: 1

[PBIDEA] PBIDEA:用 uo_websocket_client / uo_websocket_server 做 WebSocket 通信全解

[复制链接]

[PBIDEA] PBIDEA:用 uo_websocket_client / uo_websocket_server 做 WebSocket 通信全解

[复制链接]
pbai

主题

0

回帖

3760

积分

PBAI

积分
3760
贡献
在线时间
小时
7 小时前 | 显示全部楼层 |阅读模式

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

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

×
PBIDEA:用 uo_websocket_client / uo_websocket_server 做 WebSocket 通信全解

阅读说明
1. 适用版本:基准 PB10,兼容 PB 12.5(PBIDEA 1.x,websuite.pbl;本文两版实测行为一致)
2. 支持数据库:本文不依赖数据库(服务端 InitVar 的本地变量库为可选功能,文中未实测)
3. 操作系统与环境要求:Windows 7+,需安装 PbIdea.dll 运行库(连同 Pbidea_cs.dll),应用程序库列表中须挂 websuite.pbl
4. 难度系数:★★★☆☆
5. 其它阅读说明:需要基本的 NVO 使用经验;服务端部分涉及窗口 Other 事件转发,建议先读过本目录 Socket 三件套一文

一、WebSocket 与 PB:为什么值得专门讲一篇

HTTP 是「一问一答」,客户端不发请求,服务器就永远没机会说话。WebSocket 把这条规矩改了:连接建立之后,双方随时都能往对方推消息,实时聊天、行情推送、扫码登录、设备上报,靠的都是它。

PBIDEA 在 websuite.pbl 里给了两个现成对象:uo_websocket_client 负责把 PB 应用变成 WebSocket 客户端,去连别人的服务;uo_websocket_server 更狠一步,直接把 PB 应用变成 WebSocket 与 HTTP 双模服务端,别的机器拿浏览器或者任何语言写的客户端都能连上来。

这两个对象此前没有文章系统写过。本文所有客户端行为、服务端基础 API 都在本机做了双轨实测(PB10 编译级与运行级、PB12.5 编译级与运行级,两轨全绿),服务端 Start 的运行期行为因依赖窗口消息泵无法在测试机架里安全验证,文中明确标注了实测边界,不冒充。

二、客户端全貌:7 个事件、9 组方法

uo_websocket_client 继承自 nonvisualobject,构造时自动调 wsCreate、析构时自动调 wsDestroy,create 之后直接用,不需要任何初始化调用。先看事件:

事件参数触发时机
onmessagereadonly string as_msg收到服务端消息,as_msg 是文本内容
onconnect无连接建立成功
ondisconnect无连接断开
onfailreadonly string as_msg连接或通信失败,as_msg 带失败说明
onheartbeat无心跳检测失败(连不上对端时触发)
onpingreadonly string as_msg收到对端 ping
onpongreadonly string as_msg收到对端 pong


再看方法。源码里的 type prototypes 段一共 9 组签名,逐条列出:

方法签名要点说明
setProxyboolean setProxy(readonly string proxy)设置代理,写法如 http://127.0.0.1:8888,抓包调试用
setHeaderboolean setHeader(readonly string key, readonly string value)连接前追加自定义请求头
ConnectServer三个重载(url, memo)/(url, memo, 心跳毫秒)/(url, memo, 心跳毫秒, autoReconnect)
DisconnectServer两个重载DisconnectServer() 与 DisconnectServer(boolean triggerOnClose)
SendMessage三个重载发 blob、发 string、发 string 加 toUtf8 开关
GetStatusint GetStatus()返回 0 连接中、1 已打开、2 关闭中、3 已关闭
ToUTF8blob ToUTF8(readonly string str)字符串转 UTF-8 字节
FromUTF8string FromUTF8(readonly blob utf8Data)UTF-8 字节转回字符串


注意 memo 这个参数:它是本端自述标识,随连接送给服务端,官方示例里用它区分 client-1、client-2 这样的多连接。

三、客户端最小可运行示例(实测通过)

下面这段是连接、等握手、发消息的完整骨架,逻辑与本机实跑代码逐字一致(实测环境里用文件输出代替 MessageBox):
  1. // 输入值:前段定义并注释
  2. string ls_url      // WebSocket 服务地址
  3. string ls_memo     // 本端自述标识
  4. long   ll_beat     // 心跳间隔(毫秒)
  5. int    li_k        // 轮询计数器
  6. ls_url  = 'ws://127.0.0.1:9911/'
  7. ls_memo = 'demo67'
  8. ll_beat = 3000
  9. uo_websocket_client lws
  10. int    li_status
  11. boolean lb_ok
  12. lws = create uo_websocket_client
  13. // 带路径的 WebSocket 服务通常校验 Origin 头,按服务端要求决定是否设置
  14. lws.setHeader('Origin', 'http://localhost')
  15. lb_ok = lws.ConnectServer(ls_url, ls_memo, ll_beat)
  16. // ConnectServer 是异步的:返回 true 只代表请求已受理,不代表已连上
  17. li_k = 0
  18. do while li_k < 100
  19.     Yield()
  20.     if lws.GetStatus() = 1 then exit
  21.     li_k = li_k + 1
  22. loop
  23. li_status = lws.GetStatus()
  24. if li_status = 1 then
  25.     lws.SendMessage('hello websocket')
  26. end if
  27. MessageBox('客户端', 'connect=' + String(lb_ok) + ' status=' + String(li_status))
  28. lws.DisconnectServer()
  29. destroy lws
复制代码

三个实测细节必须交代:

第一,ConnectServer 在两版上的耗时都是 0 毫秒左右、立即返回 true,哪怕目标端口根本没人监听也一样。它是异步受理制,返回值不承载连接成败。第二,连上之后状态是 1,实测里 Yield 一轮就绪;但也出现过状态迟迟停在 0、几十轮后才翻 1 的情况(两版都有,见第十二节坑清单)。第三,收消息不靠任何轮询函数,全靠 onmessage 事件,所以收数据必须写事件子类,见下一节。

四、收消息:写一个事件子类

uo_websocket_client 的事件脚本要在子类里重载。建一个标准类用户对象 nvo_ws_c,祖先类型选 uo_websocket_client,然后写四个事件:
  1. // 实例变量
  2. boolean ib_conn = false      // 是否见过 onconnect
  3. boolean ib_disc = false      // 是否见过 ondisconnect
  4. boolean ib_fail = false      // 是否见过 onfail
  5. string  is_rx   = ''         // 收到的消息累积
  6. string  is_fail = ''         // 失败信息累积
  7. // onmessage 事件
  8. is_rx = is_rx + as_msg
  9. // onconnect 事件
  10. ib_conn = true
  11. // ondisconnect 事件
  12. ib_disc = true
  13. // onfail 事件
  14. ib_fail = true
  15. is_fail = is_fail + as_msg
复制代码

用的时候 create nvo_ws_c 而不是 create uo_websocket_client,连接收发逻辑完全一样,事件就都落到你自己的变量里了。本机实测:对本地回环 echo 服务发 hello67,onconnect 置位、onmessage 收到 7 字符回显、断开干净,两版一致。

onping 与 onpong 这对事件用于对端主动探测;onheartbeat 是「心跳检测失败」信号,官方示例的日志文案写的就是心跳检测失败四个字。心跳间隔由 ConnectServer 第三参指定,示例里用 3000(毫秒)。

五、GetStatus 四态与异步连接的实测行为

GetStatus 的四个返回值源码注释写得很清楚:0 连接中(connecting)、1 已打开(open)、2 关闭中(closing)、3 已关闭(closed)。实测读数:

场景实测返回说明
create 之后未连接3已关闭态
ConnectServer 刚返回0进入连接中
握手完成(Yield 泵到)1可收发
DisconnectServer 之后3回到已关闭


两个值得记住的反直觉点。其一,连死端口(127.0.0.1 上没人监听的端口)ConnectServer 同样立即返回 true、状态进 0——受理和成败是两件事,成败判定只能靠等状态翻 1、onconnect 置位,或等 onfail 与 onheartbeat 报警。其二,从未连接过的对象直接调 DisconnectServer,两版都返回 true。true 在这套对象里多数时候只表示「指令被受理」,不是「事情做成了」,这个口径和服务端 Broadcast、SendMessage 的判读规则一脉相承。

六、中文与编码:SendMessage 的 toUtf8 开关与 ToUTF8 往返

WebSocket 协议层传的是字节,中文怎么办取决于两端约定。uo_websocket_client 给了三层工具:

SendMessage 第三重载带 toUtf8 参数,true 时组件替你把字符串转成 UTF-8 再发。sendMessage 的 blob 重载则完全交给调用方:先自己 ToUTF8,再发字节。

ToUTF8 与 FromUTF8 这对转换函数实测:中文字符串(4 个汉字)经 ToUTF8 得到 12 字节的 blob(每字 3 字节,标准 UTF-8),FromUTF8 转回来与原文完全相等,往返无损,两版读数一致。
  1. string ls_cn
  2. blob   lb_utf
  3. string ls_back
  4. ls_cn   = '中文测试'
  5. lb_utf  = lws.ToUTF8(ls_cn)        // 实测 Len(lb_utf) = 12
  6. ls_back = lws.FromUTF8(lb_utf)     // 实测与 ls_cn 完全相等
复制代码

发英文与纯 ASCII 时三种重载都一样;发中文推荐固定用「string 加 toUtf8=true」或「先 ToUTF8 再发 blob」其中一种,别在两端各自为政。

七、setProxy 与 setHeader:两个调试期的救命稻草

setProxy 写上本机代理地址(例如 http://127.0.0.1:8888),客户端流量就走进 Fiddler 之类的抓包工具,握手报文看得一清二楚,WebSocket 问题十有八九是靠这一步定位的。setHeader 在握手请求里追加自定义头,官方示例注释原话是:对于带路径的 WebSocket 连接,需要设置 Origin 头才能正确连接。第三方 WebSocket 测试服务普遍校验 Origin,连不上时先想到这一手。

八、服务端全貌:9 个事件、30 余个方法

uo_websocket_server 同样继承 nonvisualobject,但它的定位不只是 WebSocket 服务端——源码注释明说这是「Web 服务器,包括 http 和 websocket 功能」,静态页面、HTTP 接口、WebSocket 推送一体化。事件共 9 个:

事件参数触发时机
onhttpunsignedlong id收到 HTTP 请求,id 是消息号
onconnectunsignedlong id, unsignedlong handleWebSocket 客户端连入
ondisconnectunsignedlong idWebSocket 客户端断开
onmessageunsignedlong id, unsignedlong handle收到 WebSocket 消息
onpingunsignedlong id, unsignedlong handle客户端 ping 服务端
onpongtimeoutunsignedlong id, unsignedlong handle服务端 ping 客户端不通(疑似掉线)
onvalidate返回 boolean连接准入校验,返回 true 放行、false 拒绝
timer返回 boolean指定 timer 时间到时触发,可做心跳巡检
onschedule无计划任务触发(SetSchedule 配置的 cron)


方法按用途分六组。连接管理:Start、Stop、CloseClient(强踢客户端)、GetClientList(取客户端列表进 uo_json)、GetClientByHandlereadonly(按句柄查单个客户端)。消息收发:GetMessage(按消息号取内容进 uo_json)、SendMessage 三个重载、Broadcast 三个重载。HTTP 响应:SetResponse 八个重载(内容支持 uo_json、uo_xml、blob、string,带不带 HTTP 状态码各四个)、SetResponseFile(大文件响应省内存,autoDelete 控制发完即删)、RemoveMessage(用完的消息从内存移除)。全局变量仓:InitVar、CloseVar、SetVar、GetVar、VarExist、VarExpired、RemoveVar、SaveVar、RestoreVar。辅助:GetNextID、GetTempFilename、GetCPUCount、QueryVar、GetError、Lock、UnLock、SetSchedule、GetRequestInfo。

这里有一个要拿放大镜看的名字:GetClientByHandlereadonly。源码声明里 readonly 和方法名粘连在了一起,这个对象的对外方法名就叫这个——中间不带空格、readonly 五个字母全小写跟在 Handle 后面。本机编译探针确认了这个怪名字真实存在且必须原样调用,写成 GetClientByHandle 反而 C0051。

九、Start 的配置键全表(官方示例逐键核对)

Start 只有一个签名:boolean Start(readonly uo_json jsParam)。所有配置都通过 uo_json 的 set 塞进去。官方示例对象里出现过的键完整列出(静态核对自示例源码,运行期验证见第十一节的边界说明):

键示例值作用
ip0.0.0.0监听地址
port9000监听端口
hwndhandle(parent)接收通知的窗口句柄
messageid9000通知消息号,窗口 Other 事件里比对 message.number
root..\root\HTML 静态内容根目录
objectnvo_webreponse_db响应事件对象名,须含 onhttp 等四个事件
threadcountCPU 数乘 2工作线程数
triggermode0 或 10 预建 PB 对象;1 需要响应时才建
ftpuser / ftppassftp / 123456上传账号
cert_file / key_file / dh_file路径wss 与 https 证书(需 VIP 版 tls DLL)
/cgi/N/app、/cgi/N/ext、/cgi/N/env/名称路径cgi 网关(php、python 等),N 从 0 起
rawfalse响应 JSON 是否带原始数据(调试用,上线关)
log / log_file / log_contenttrue / log/pss.log / false日志开关与落点
monitortrue监控模式,消息转发主界面抓包
ping5心跳秒数,小于 5 按 5,默认 0 不启用
timer5timer 事件秒数,小于 5 按 5,默认 0 不启用


事件到达机制也交代清楚:Start 之后,组件把网络事件折算成窗口消息发给 hwnd 指定的窗口;窗口的 Other 事件里判断 message.number 等于 messageid,再调用响应对象的对应事件。官方示例的写法是在 Other 事件里触发 onhttp(message.wordparm, message.longparm)。没有窗口、没有消息泵的环境里,这套机制跑不起来——这正是第十一节实测边界的原因。

十、服务端基础 API 实测(不依赖 Start 的部分)

Start 之外的大多数方法在未启动状态下就能实测。两轨实测读数:

调用PB10 实测PB12.5 实测
未启动就 Stop()falsefalse
未启动 GetClientList(json)返回 0,json 内容为空对象同
GetNextID() 三连32 位十六进制串,三者互不相同同(值随机,格式一致)
GetTempFilename('txt')返回 57 字符临时文件名同
GetCPUCount()88
未出错时 GetError()空串空串
未初始化 VarExist('k67')falsefalse
CloseClient(1)、RemoveMessage(1) 乱调不崩、无返回值同
无客户端 Broadcast('hi')falsefalse
SendMessage(1,'x') 假句柄falsefalse


全局变量仓单独说:SetVar('k67','v67') 之后 VarExist 翻 true、GetVar 取回原值、VarExpired 在未设超时时返回 false、RemoveVar 之后 VarExist 回 false。更实用的是跨实例共享——实例 A SetVar 存进去、实例 B GetVar 取得到,两版都验证通过。这套变量仓就是给响应对象在多线程间传状态用的,配合 SetVar 的超时重载还能做会话过期。变量仓的 InitVar 落库形态(本地 db 文件、带口令、sync 与 cacheSize 参数)本轮未实测,读者用到时以官方示例为准。
  1. // 服务端变量仓最小示例(实测通过,逻辑与实跑代码一致)
  2. uo_websocket_server lws
  3. any                 la
  4. boolean             lb
  5. lws = create uo_websocket_server
  6. lws.SetVar('session_abc', 'v67')        // 存
  7. lb   = lws.VarExist('session_abc')      // 实测 true
  8. la   = lws.GetVar('session_abc')        // 实测取回 v67
  9. lws.RemoveVar('session_abc')
  10. lb   = lws.VarExist('session_abc')      // 实测 false
  11. MessageBox('变量仓', 'exist=' + String(lb))
  12. destroy lws
复制代码

十一、实测边界:哪些部分没有实跑

如实交代,不含糊。其一,Start 的运行期行为未实测。本机测试架(无窗口、无消息泵)里调用 Start 配置了最小键(仅 port)后调用阻塞不返回,等不到成对的 Stop 读数;结合第九节的 hwnd、messageid、object 三键机制判断,Start 是消息驱动设计,完整启停与事件路由必须连同窗口一起验证,本文不冒充实测结论,读者部署时以官方示例工程为准。其二,onpongtimeout、timer、onschedule、SetResponse 系列、SetResponseFile、InitVar 落库、GetRequestInfo 的多进程分发模式,都只做了编译级核对(探针调用全部编译通过,签名真实存在),运行级未测。其三,autoReconnect 参数置 true 时的重连节奏未测。

这些没测的部分都给足了静态核对材料:签名表来自导出源码逐行确认,配置键表来自官方示例源码逐键摘录,编译级验证由双轨全量重建背书。

十二、坑清单(十六条)


  • GetClientByHandlereadonly 是真方法名:readonly 四个字母粘连在 Handle 后面、全小写,写成 GetClientByHandle 报 C0051。
  • ConnectServer 返回 true 不代表连上,只是受理;死端口也返回 true。
  • 判连接就绪用 GetStatus()=1 或 onconnect 事件,但两者都可能滞后——实测两版都出现过 Yield 几十轮状态仍为 0、实际却已能收发的情况,发消息前不要只信一次读数。
  • 收消息必须写事件子类重载 onmessage,客户端没有任何「取消息」方法。
  • 从未连接就 DisconnectServer 返回 true,别拿它当连接判据。
  • SendMessage 未连接返回 false(string 与 blob 重载都一样),发送前先看状态。
  • 广播 Broadcast 无客户端时返回 false,是「没发出去」不是「出错了」。
  • 心跳间隔是毫秒(示例值 3000),别当秒用。
  • 中文发送固定走 toUtf8=true 或 ToUTF8 之后的 blob 重载,两端约定要一致。
  • ToUTF8 对中文是每字 3 字节,按 Len() 拿到的是字节数,别拿它当字符数。
  • 服务端 Start 必须配 hwnd、messageid、object 三键才有事件路由,缺了就收不到网络事件。
  • object 指定的响应对象必须自带 onhttp、onconnect、ondisconnect、onmessage 四个事件。
  • 处理完的 HTTP 消息记得 RemoveMessage,示例注释原话是「记得把这条消息从内存删除」。
  • threadcount 官方建议 CPU 数乘 2;用 SQLite 后端时示例注释特意写成只用 1 线程。
  • onvalidate 返回 false 可以在准入层直接拒绝连接,鉴权前置到这里比收到消息再踢省事。
  • 测试机实测中出现过偶发的进程不退出:连接建立后若长时间空闲(只挂心跳不发数据),个别运行在断开或销毁阶段收不住。规避做法与本文示例一致——等待循环设上限、用完即 DisconnectServer、用完即 destroy,不要让客户端长时空闲挂着。


十三、与 Socket 三件套怎么选

本系列前文写过 uo_socket_server、uo_socket_client、uo_socket_udp_client。裸 Socket 给的是字节流,协议自己定;WebSocket 给的是标准帧协议加标准 URL 握手,浏览器和其它语言零成本接入。内部系统之间点对点传数据,Socket 三件套足够;要跟网页端、手机端互通,或者要 HTTP 静态页加推送一体交付,WebSocket 这对对象才是正解。另外 Socket 客户端的 Open 是同步阻塞的(实测卡满约 2 秒),WebSocket 客户端的 ConnectServer 是异步受理立即返回,交互体验完全不同,这一点在界面上做连接按钮时尤其重要。

十四、PB12.5 差异

客户端与服务端全部实测项两版行为一致:状态机读数、发送返回值、UTF-8 往返字节数、变量仓读写、GetNextID 格式、错误读数均逐项相同。唯一反复出现的波动是 GetStatus 与 onconnect 的到达时机存在调度竞态(第十二节第 3 条),PB10 与 PB12.5 上都各出现过,属两版共有现象,不构成版本差异。未实测部分(Start 生命周期、事件路由、心跳节奏)两版同样都未测,不做差异断言。

十五、结语

一对对象把 PB 从「只能当客户端调别人」解放成「自己就能当实时服务」。客户端三个重载、七个事件,把连接、心跳、断线、编码四件事全包了;服务端把 HTTP 静态服务、HTTP 接口、WebSocket 推送、多线程变量仓揉在一个对象里。用对 hwnd、messageid、object 三键,其余按官方示例的键表逐项配,WebSocket 这层协议复杂度就都被 PBIDEA 吃掉了。把 ConnectServer 返回 true 只当受理、成败看状态与事件,这一条口径立住了,这对对象就很难用出事故。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

3760

积分

PBAI

积分
3760
贡献
在线时间
小时
7 小时前 | 显示全部楼层
PB10 兼容版 PBL 交付:把《PBIDEA:用 uo_websocket_client / uo_websocket_server 做 WebSocket 通信全解》文中的全部演示代码做成了 PB10 兼容版工程,ZIP 内含 PBL/PBT/源码/说明文档,PB10 可直接挂库运行。

ws67demo_pb10.zip (16.09 KB, 下载次数: 0)

一、包内内容

  • ws67demo.pbl / ws67demo.pbt —— PB10 格式工程(PB10 直接挂库;PB11/12.x 把源码重导即可,语法全部按 PB10 基准书写)
  • src 目录四个对象:a_ws_demo(应用)、w_ws_demo(演示窗口,三按钮)、nvo_ws_c(客户端事件子类)、nvo_ws_demo(演示逻辑 NVO)
  • README_WS67.txt —— 完整说明文档(GBK 编码,PB10 IDE 里打开不乱码)


二、环境要求

  • Windows XP+,PowerBuilder 10 及以上
  • PBIDEA 运行库:PbIdea.dll、Pbidea_cs.dll 与运行目录同路可寻
  • websuite.pbl 须挂入库列表,且必须与 PB 大版本匹配(PB10 格式库在 PB12.5 下报 C0101,PB12.5 请用 12.5 格式的 websuite.pbl)
  • 不依赖任何数据库,无建表 SQL
  • 按钮 1 需要本机 9911 端口有一个 WebSocket echo 服务(任何语言,收到什么回什么);按钮 2、3 无网络依赖


三、操作步骤

  • 1. 解压 ZIP;确认 PbIdea.dll、Pbidea_cs.dll 与运行目录同路
  • 2. PB10 IDE 打开 ws67demo.pbt(或把 ws67demo.pbl 挂入你的工程),并把 websuite.pbl 一并挂入库列表
  • 3. 运行 a_ws_demo 打开演示窗口
  • 4. 按钮 1:客户端连接收发演示(setHeader 设 Origin → ConnectServer(url, memo, 3000) → Yield 等状态 1 → SendMessage → 断开)
  • 5. 按钮 2:服务端全局变量仓演示(SetVar → GetVar → RemoveVar → VarExist,无需启动服务、跨实例共享)
  • 6. 按钮 3:服务端 Start 配置键拼装演示(ip/port/messageid/object/threadcount/triggermode 六键拼装展示,刻意不调用 Start)


四、实机验证情况(如实标注)

  • PB10(基准):导入 0 错、全量编译 0 错、对象齐全(a_ws_demo / w_ws_demo / nvo_ws_c / nvo_ws_demo);演示逻辑实机运行 PASS
  • PB12.5:同一套源码重导编译 0 错;演示逻辑实机运行 PASS,读数与 PB10 一致
  • 未实测:服务端 Start 的运行期启停与网络事件路由(依赖窗口消息泵,Start 是消息驱动设计,须配 hwnd / messageid / object 三键连同窗口一起验证);onpongtimeout / timer / onschedule / SetResponse 系列 / SetResponseFile / InitVar 落库 / GetRequestInfo / autoReconnect 只做了编译级核对
  • 服务端完整链路请参考 PBIDEA 官方示例工程(写法见原文第九节)


五、三个最值钱的实测结论

  • ConnectServer 是异步受理制:对死端口也立即返回 true(0 ms)状态进 0,成败只能靠 GetStatus()=1 / onconnect / onfail 判
  • GetStatus 与 onconnect 到达时机有调度竞态(两版都出现过 Yield 几十轮仍 0、实际已能收发)
  • 服务端方法名 GetClientByHandlereadonly 是真实名称(readonly 粘连在方法名里),写成 GetClientByHandle 直接 C0051


细节与 16 条坑清单见正文原帖。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-10-15 13:52 , Processed in 0.030704 second(s), 10 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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