這週整週都在搞 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