[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"i-ri:search-line":3,"i-ri:moon-line":8,"i-ri:sun-line":10,"i-ri:menu-3-line":12,"i-ri:close-line":14,"notes-module-items-im":16,"notes-page-im-02-connection-protocol\u002F03-connection-lifecycle":66,"i-ri:arrow-left-s-line":311,"i-ri:menu-2-line":313,"i-ri:arrow-right-s-line":315},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath fill=\"currentColor\" d=\"m18.031 16.617l4.283 4.282l-1.415 1.415l-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9s9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617m-2.006-.742A6.98 6.98 0 0 0 18 11c0-3.867-3.133-7-7-7s-7 3.133-7 7s3.133 7 7 7a6.98 6.98 0 0 0 4.875-1.975z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"currentColor\" d=\"M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2h.1A6.98 6.98 0 0 0 10 7m-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938A8 8 0 0 0 4 12\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":11},"\u003Cpath fill=\"currentColor\" d=\"M12 18a6 6 0 1 1 0-12a6 6 0 0 1 0 12m0-2a4 4 0 1 0 0-8a4 4 0 0 0 0 8M11 1h2v3h-2zm0 19h2v3h-2zM3.515 4.929l1.414-1.414L7.05 5.636L5.636 7.05zM16.95 18.364l1.414-1.414l2.121 2.121l-1.414 1.414zm2.121-14.85l1.414 1.415l-2.121 2.121l-1.414-1.414zM5.636 16.95l1.414 1.414l-2.121 2.121l-1.414-1.414zM23 11v2h-3v-2zM4 11v2H1v-2z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":13},"\u003Cpath fill=\"currentColor\" d=\"M3 4h18v2H3zm6 7h12v2H9zm-6 7h18v2H3z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":15},"\u003Cpath fill=\"currentColor\" d=\"m12 10.587l4.95-4.95l1.414 1.414l-4.95 4.95l4.95 4.95l-1.415 1.414l-4.95-4.95l-4.949 4.95l-1.414-1.415l4.95-4.95l-4.95-4.95L7.05 5.638z\"\u002F>",[17,22,26,30,34,38,42,46,50,54,58,62],{"path":18,"title":19,"meta":20},"\u002Fnotes\u002Fim\u002F01-message-state\u002F01-offline-sync","连接恢复了，离线消息为什么还没补齐？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},"2026-11-05T09:47:15.000Z",{"path":23,"title":24,"meta":25},"\u002Fnotes\u002Fim\u002F01-message-state\u002F02-read-cursor","手机读过了，电脑为何又显示未读？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":27,"title":28,"meta":29},"\u002Fnotes\u002Fim\u002F01-message-state\u002F03-pagination-search","聊天搜索跳回旧消息后，怎样继续翻页？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":31,"title":32,"meta":33},"\u002Fnotes\u002Fim\u002F01-message-state\u002F04-unread-refresh","消息没停，未读角标就不更新？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":35,"title":36,"meta":37},"\u002Fnotes\u002Fim\u002F02-connection-protocol\u002F01-stream-parsing","一次接收回调，为什么不是一条消息？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":39,"title":40,"meta":41},"\u002Fnotes\u002Fim\u002F02-connection-protocol\u002F02-request-response","长连接上的请求，怎样等到自己的响应？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":43,"title":44,"meta":45},"\u002Fnotes\u002Fim\u002F02-connection-protocol\u002F03-connection-lifecycle","登录一直在连接中，到底在等什么？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":47,"title":48,"meta":49},"\u002Fnotes\u002Fim\u002F03-message-content\u002F01-file-download","附件下载完了，为什么还是打不开？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":51,"title":52,"meta":53},"\u002Fnotes\u002Fim\u002F03-message-content\u002F02-image-send","图片上传到 100%，为什么还没发出去？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":55,"title":56,"meta":57},"\u002Fnotes\u002Fim\u002F03-message-content\u002F03-mentions","聊天 @提及怎样保存身份和位置？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":59,"title":60,"meta":61},"\u002Fnotes\u002Fim\u002F04-client-release\u002F01-flutter-build","Release 包为什么仍连着测试环境？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":63,"title":64,"meta":65},"\u002Fnotes\u002Fim","即时通信",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"id":67,"type":68,"sourcePath":69,"routePath":43,"path":43,"title":44,"description":70,"updatedAt":21,"meta":71,"body":80,"toc":303},"bm90ZTpub3Rlcy9pbS8wMi1jb25uZWN0aW9uLXByb3RvY29sLzAzLWNvbm5lY3Rpb24tbGlmZWN5Y2xlLm1k","note","notes\u002Fim\u002F02-connection-protocol\u002F03-connection-lifecycle.md","聊天 App 登录时，Socket 连上只是开始。还要初始化 XMPP 协议流、认证账号、绑定当前客户端的资源标识，才能正常收发业务消息。",{"id":67,"type":68,"sourcePath":69,"slug":72,"title":44,"updatedAt":21,"description":70,"tags":73,"date":77,"comment":78,"summary":79,"lastUpdated":21},"im\u002F02-connection-protocol\u002F03-connection-lifecycle",[74,75,76],"dart","IM","network","2026-08-22",true,"网络连通、协议可用和历史恢复各有阶段。明确每轮超时与整体预算，再检查旧尝试和清理逻辑是否真的结束。",{"type":81,"children":82},"root",[83,91,96,101,108,113,127,132,138,143,148,240,245,250,256,272,277,282,288,293,298],{"type":84,"tag":85,"props":86,"children":88},"element","h1",{"id":87},"登录一直在连接中到底在等什么",[89],{"type":90,"value":44},"text",{"type":84,"tag":92,"props":93,"children":94},"p",{},[95],{"type":90,"value":70},{"type":84,"tag":92,"props":97,"children":98},{},[99],{"type":90,"value":100},"如果这些步骤都叫“连接中”，用户遇到长时间等待，日志也很难指出卡在哪。这个 Dart 客户端把连接阶段分开记录，但超时、重试和资源清理仍有几处容易误解的地方。",{"type":84,"tag":102,"props":103,"children":105},"h2",{"id":104},"网络连上了为什么还说未连接",[106],{"type":90,"value":107},"网络连上了，为什么还说未连接？",{"type":84,"tag":92,"props":109,"children":110},{},[111],{"type":90,"value":112},"可以只对外提供一个可用状态，也可以保留详细阶段，再从中计算是否可收发。项目保留断开、连接中、网络已连接、认证中、认证完成和准备就绪等状态。",{"type":84,"tag":92,"props":114,"children":115},{},[116,118,125],{"type":90,"value":117},"对外的 ",{"type":84,"tag":119,"props":120,"children":122},"code",{"className":121},[],[123],{"type":90,"value":124},"isConnected",{"type":90,"value":126}," 实际判断是否 ready，表示协议初始化完成。因此它为 false 时，Socket 未必断了，可能仍在认证或绑定。",{"type":84,"tag":92,"props":128,"children":129},{},[130],{"type":90,"value":131},"底层发送也不能全部等 ready，因为认证和绑定本身就要发协议数据。当前状态更新主要是赋值与广播，合法转换仍散落在调用流程中；定义了状态枚举，还没有自动形成统一的转换约束。",{"type":84,"tag":102,"props":133,"children":135},{"id":134},"设了-20-秒超时为什么登录更久",[136],{"type":90,"value":137},"设了 20 秒超时，为什么登录更久？",{"type":84,"tag":92,"props":139,"children":140},{},[141],{"type":90,"value":142},"超时可以限制一次阶段、一次连接尝试，或者整个登录过程。三种预算需要明确，不能看到一个数字就把它当成总上限。",{"type":84,"tag":92,"props":144,"children":145},{},[146],{"type":90,"value":147},"项目的 Token 登录按阶段等待，当前配置如下：",{"type":84,"tag":149,"props":150,"children":151},"table",{},[152,171],{"type":84,"tag":153,"props":154,"children":155},"thead",{},[156],{"type":84,"tag":157,"props":158,"children":159},"tr",{},[160,166],{"type":84,"tag":161,"props":162,"children":163},"th",{},[164],{"type":90,"value":165},"范围",{"type":84,"tag":161,"props":167,"children":168},{},[169],{"type":90,"value":170},"等待值",{"type":84,"tag":172,"props":173,"children":174},"tbody",{},[175,189,202,215,227],{"type":84,"tag":157,"props":176,"children":177},{},[178,184],{"type":84,"tag":179,"props":180,"children":181},"td",{},[182],{"type":90,"value":183},"网络建立",{"type":84,"tag":179,"props":185,"children":186},{},[187],{"type":90,"value":188},"10 秒",{"type":84,"tag":157,"props":190,"children":191},{},[192,197],{"type":84,"tag":179,"props":193,"children":194},{},[195],{"type":90,"value":196},"认证",{"type":84,"tag":179,"props":198,"children":199},{},[200],{"type":90,"value":201},"8 秒",{"type":84,"tag":157,"props":203,"children":204},{},[205,210],{"type":84,"tag":179,"props":206,"children":207},{},[208],{"type":90,"value":209},"协议流等待",{"type":84,"tag":179,"props":211,"children":212},{},[213],{"type":90,"value":214},"5 秒",{"type":84,"tag":157,"props":216,"children":217},{},[218,223],{"type":84,"tag":179,"props":219,"children":220},{},[221],{"type":90,"value":222},"绑定",{"type":84,"tag":179,"props":224,"children":225},{},[226],{"type":90,"value":214},{"type":84,"tag":157,"props":228,"children":229},{},[230,235],{"type":84,"tag":179,"props":231,"children":232},{},[233],{"type":90,"value":234},"单轮外围等待",{"type":84,"tag":179,"props":236,"children":237},{},[238],{"type":90,"value":239},"默认 20 秒",{"type":84,"tag":92,"props":241,"children":242},{},[243],{"type":90,"value":244},"阶段与外围计时同时存在，不能简单相加，外围可能先超时。外围计时又在重试循环内部，每轮重新开始；最多两次重试，加首次共三轮，因此整个登录没有被限制在 20 秒内。",{"type":84,"tag":92,"props":246,"children":247},{},[248],{"type":90,"value":249},"重试间隔从 800 毫秒起按倍数增加并封顶 5 秒，这条路径两次分别等 800 毫秒和 1.6 秒。若要限制总耗时，可以在入口记录截止时间，各阶段共用剩余预算，当前实现尚未如此处理。",{"type":84,"tag":102,"props":251,"children":253},{"id":252},"外围超时为什么旧连接还可能回来",[254],{"type":90,"value":255},"外围超时，为什么旧连接还可能回来？",{"type":84,"tag":92,"props":257,"children":258},{},[259,261,270],{"type":90,"value":260},"项目用连接操作与超时进行 ",{"type":84,"tag":262,"props":263,"children":267},"a",{"href":264,"rel":265},"https:\u002F\u002Fapi.dart.dev\u002Fdart-async\u002FFuture\u002Fany.html",[266],"nofollow",[268],{"type":90,"value":269},"Future.any",{"type":90,"value":271}," 竞争，最先结束的决定结果，但其他任务不会自动取消。第一轮外围已经超时，内部认证或绑定仍可能稍后完成。",{"type":84,"tag":92,"props":273,"children":274},{},[275],{"type":90,"value":276},"如果旧尝试继续修改 Socket、连接状态或 Token，就可能干扰下一轮。可选做法是为每次尝试保留身份，结果回来时只允许当前尝试生效，并实际清理可取消资源。项目的消息补偿层已有账号和代次检查，底层连接尝试仍需补类似约束。",{"type":84,"tag":92,"props":278,"children":279},{},[280],{"type":90,"value":281},"心跳也不是精确的总计时。当前单次等 15 秒，连续三次失败进入连接丢失处理，另有定时调度与重试路径，不能直接理解成 45 秒必然断线。多个恢复入口尚未统一安排，也需要避免各跑一套重试。",{"type":84,"tag":102,"props":283,"children":285},{"id":284},"清理函数调用了为什么资源可能没释放",[286],{"type":90,"value":287},"清理函数调用了，为什么资源可能没释放？",{"type":84,"tag":92,"props":289,"children":290},{},[291],{"type":90,"value":292},"外层清理入口先把“正在清理”标记设为 true，再调用内层函数；内层开头又发现该标记为 true，于是直接返回。防重复的逻辑反而挡住了真正的释放过程。",{"type":84,"tag":92,"props":294,"children":295},{},[296],{"type":90,"value":297},"这条问题出现在外层调用内层的路径，直接调用内层的入口需要另外检查。后续可以由外层管理重复调用，内层只负责释放；验收看定时器、订阅、Socket 和排队任务是否结束，而不只看状态是否变成断开。",{"type":84,"tag":92,"props":299,"children":300},{},[301],{"type":90,"value":302},"错误分类也尚有不足：认证错误会阻止重试，但最终包装可能让其他错误也表现成认证异常，部分判断还依赖异常文本。需要保留阶段和明确错误类型，才能让界面区分重新登录与稍后重连。",{"title":304,"searchDepth":305,"depth":305,"links":306},"",2,[307,308,309,310],{"id":104,"depth":305,"text":107},{"id":134,"depth":305,"text":137},{"id":252,"depth":305,"text":255},{"id":284,"depth":305,"text":287},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":312},"\u003Cpath fill=\"currentColor\" d=\"m10.828 12l4.95 4.95l-1.414 1.415L8 12l6.364-6.364l1.414 1.414z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":314},"\u003Cpath fill=\"currentColor\" d=\"M3 4h18v2H3zm0 7h12v2H3zm0 7h18v2H3z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":316},"\u003Cpath fill=\"currentColor\" d=\"m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z\"\u002F>"]