這週整週都在搞 MSN,想要替便當系統加個即時通知的功能 (團購開啟後,用 MSN 通知所有人)。 基本上是採用韓國開發的 JMsn Library來實作, 在 web server 這一端養一隻 msn robot,然後利用 robot發送訊息,以下僅摘錄發送信息的程式碼片段給大家參考:
public boolean testSendMessage(final MSNMessenger messenger, final String account, final String message) { MsnAdapter sender = null; try { //確認訊息已發出,用 countdown 1 final CountDownLatch sentLatch = new CountDownLatch(1); 建立 listener 等待 session 開啟 sender = new MsnAdapter() { //當好友被繳請後,會通知 (呼叫) 這個 method 。 @Override public void whoJoinSession(SwitchboardSession allSession, MsnFriend msnfriend) { if (!msnfriend.getLoginName().equals(account)) { return; //如果是繳請別人,則離開 } //如果已通知過了,則離開。 if (sentLatch.getCount() == 0) return; try { //送訊息 allSession.sendInstantMessage(new MimeMessage(message)); //送完訊息後倒數為 0,通知主 thread 已經通知完成了。 sentLatch.countDown(); logger.debug("done"); } catch (Exception e) { logger.error("failed", e.getMessage()); } } }; //將 listner 加進 MSNMessenger 內,這樣就能接到 server 端的通知。 messenger.addMsnListener(sender); //呼叫好友 messenger.doCall(account); //等待 20 秒,如果沒有回應則跳出。 if (sentLatch.await(20, TimeUnit.SECONDS)) { return true; //成功 } else { logger.debug("fail to call friend " + account ); return false; //失敗 } } catch (Exception e) { logger.error(e.getMessage(), e); throw e; } finally { //將 session 和 listener 清除。 if (messenger != null) { if (sender != null) { try { messenger.removeMsnListener(sender); } catch (RuntimeException ignore) {} } try { SwitchboardSession openedSession = messenger .findSwitchboardSessionAt(account); if (openedSession != null) { openedSession.cleanUp(); } } catch (RuntimeException ignore) {} } } }
簡單來說,就是替 MSNMessenger 先掛上一個 listener,然後以 MSNMessenger 繳請好友, 開一個 SwitchboardSession。待繳請成功之後,listener 的 whoJoinSession() 會被 fired,接著再發送 訊息。最後一步再把開啟的 session 和 listener 都清除乾淨。
理論上是這樣做啦,實際運作時也是行得通,可惜的是 MSN 的 Server 會擋 "rapid request",換句話說, 如果你開太多連線,太多 session,request 過多... etc,後面的 request 都會被 reject ,server 會回傳個 800 錯誤訊息給你。那多少算太多?我自己測試,大概 10 個 session,MSN server 就會 reject, 我還另外減慢 request 的頻率,改成每 5 秒開一次 session,但是 MSN server 還是 reject...
上限只有 10 個,在訂便當系統裡,一次團購就十幾二十個人,要同時通知就得開 20 個 session, 更何況同時間會有好幾個群組團購... 所以就玩完啦... 唉...不知有無其他方法可以規避 MSN server 的限制? 現在 MSN 行不通了,我還想過像是 RSS 和 Mail 等類型的服務,但它們都不具備及時性,所以不成。 而其他及時服務的安裝人數不多,而且大概也有類似的限制吧,我猜...
團購開啟時,若能夠及時的通知,呼朋引伴吸引人氣,團購的成功率便可大大的提高,可惜美夢破碎...
最後一些相關資料:
1. 目前查知 MSN 的好友人數上限是 600 人,一般人是夠用了,但如果是 robot 就不夠啦,要多建幾隻預備。
2. 目前 JMsn library (v1.3) 裡,其 MSNMessenger 在 logout() 時會有 thread leak 的問題, "即 MSNMessenger 裡的 NotificationProcessor 裡的 CallbackCleaner 在網路不正常時 logout 不會被清除", 看不懂?反正解決方法就用 reflection 硬把它殺掉就是了:
//修正後的 logout: public void fixedLogout(MSNMessenger messenger) { if (messenger != null) { Thread leakedThread = null; try { leakedThread = getLeakedThread(messenger); messenger.logout(); } catch (Exception ignore) { } finally { if (leakedThread != null) { if (!leakedThread.isInterrupted()) { leakedThread.interrupt(); } } } } } /** * current MSNMessenger do not terminate internal callback thread if * messenger not logined. */ private Thread getLeakedThread(MSNMessenger messenger) { try { Field nsField = MSNMessenger.class.getDeclaredField("ns"); nsField.setAccessible(true); NotificationProcessor ns = (NotificationProcessor) nsField .get(messenger); if (ns == null) return null; Field callbackField = NotificationProcessor.class .getDeclaredField("callbackCleaner"); callbackField.setAccessible(true); return (Thread) callbackField.get(ns); } catch (SecurityException e) { throw new RuntimeException("unexpected", e); } catch (NoSuchFieldException e) { throw new RuntimeException("unexpected", e); } catch (IllegalAccessException e) { throw new RuntimeException("unexpected", e); } }
不過我自己大概再也用不到了... orz