在Openfire上弄一個簡單的推送系統,openfire推送
推送系統
說是推送系統有點大,其實就是一個訊息廣播功能吧。作用其實也就是由服務端接收到訊息然後推送到訂閱的用戶端。
思路
對於推送最關鍵的是服務端向用戶端發送資料,用戶端向服務端訂閱自己想要的訊息。這樣的好處就是有訊息後才向用戶端推送,相比於拉取資料不會產生許多無效的查詢,即時性也高。
xmpp這種即時通訊協定基於TCP長串連還是比較符合這種情境的。只需要在服務端增加一個模組用於接收使用者訂閱與資料的推送就完成了主體功能。
在xmpp協議裡可以向外延展群組件,這樣我們寫一個組件,然後串連到xmpp伺服器,這樣就可以應用於不同的xmpp伺服器。
準備工作主要的環境
因為我比較熟悉openfire的體系,所以自然就用它。用戶端暫時沒有特別的需求,只是用於接收資料,所以用smack或者任何一款xmpp 用戶端都可以。我為了簡單就用smack寫一個簡單的代碼。
需要用到的jar包
用到的了whack的core,在maven工程裡直接引用即可,相關的依賴包會自動載入進來
<dependency> <groupId>org.igniterealtime.whack</groupId> <artifactId>core</artifactId> <version>2.0.1-SNAPSHOT</version> <type>jar</type></dependency>
核心模組推送服務
推送服務就是等待或者獲得需要推送的訊息資料後向使用者廣播出去的服務。因為這裡暫時沒有設定資料的情境,所以就簡單的用一個阻塞隊列來表示。步驟:
- 資料通過推送介面寫入到推送服務
- 推送服務將資料寫入到訊息佇列
- 發送線程檢測到訊息後取出並發給訂閱的用戶端
在此我寫了一個PushServer的類用於表示推送服務,這個類裡包含了:
- 一個訊息佇列
- 一個發送線程
- 一個訂閱列表
- 以及一些發送相關的xmpp組件
訊息佇列
//訊息列表private BlockingQueue<Packet> packetQueue;
使用到了生產者消費者模式,所以用了一個阻塞隊列,用於存放等待發送的訊息資料。
發送線程
private class PacketSenderThread extends Thread {private volatile Boolean shutdown = false;private BlockingQueue<Packet> queue;private Component component;private ComponentManager componentManager;public PacketSenderThread(ComponentManager componentManager, Component component, BlockingQueue<Packet> queue) {this.componentManager = componentManager;this.component = component;this.queue = queue;}public void run() {while (!shutdown) {Packet p;try {p = queue.take();componentManager.sendPacket(component, p);} catch (InterruptedException e1) {System.err.println(e1.getStackTrace());} catch (ComponentException e) {e.printStackTrace();}}}public void shutdown() {shutdown = true;this.interrupt();}}
這個線程繼承了Thread,線程的功能很簡單,就是一直從queue中獲得訊息,因為是阻塞的隊列,所以沒有訊息時會阻塞,一旦有訊息就會執行發送sendPacket將包發送出去。
這裡使用到了componentManager,這個是openfire實現的一個組件管理類,通過這個類的對象可以發送xmpp資料包。
增加shutdown方法,使得線程可以在外部進行退出操作。
訂閱列表
//訂閱列表private Set<JID> subscriptions;public synchronized void subscription(JID jid) {subscriptions.add(jid);}public synchronized void unsubscription(JID jid) {subscriptions.remove(jid);}
只有訂閱了這個推送服務的用戶端才會進行推送操作,這裡的代碼就是用於訂閱與退訂操作。用了一個HashSet來儲存。
xmpp組件
public class PushComponent extends AbstractComponent{public PushComponent() {}@Overridepublic String getDescription() {return "用於訊息推送服務元件,主要功能就是將訊息轉寄給具體的用戶端,實現訊息中轉的功能";}@Overridepublic String getName() {return "pusher";}@Overrideprotected void handleMessage(Message message) {}}public class PushManager {private static PushManager _instance = new PushManager();private Map<String, PushServer> pushServers;private ExternalComponentManager manager;private PushManager() {pushServers = new ConcurrentHashMap<String, PushServer>();manager = new ExternalComponentManager("192.168.149.214", 5275); manager.setSecretKey("push", "test"); manager.setMultipleAllowed("push", true);}public static PushManager getInstance() {return _instance;}public void init() { try { //初始化PushServer PushServer pushSvr = new PushServer("push", manager); pushServers.put("push", pushSvr); //註冊Component到xmpp伺服器 manager.addComponent(pushSvr.getPushDomain(), pushSvr.getComp()); } catch (ComponentException e) { e.printStackTrace(); }}public PushServer getPushServer(String pushDomain) {return pushServers.get(pushDomain);}}
這裡的PushComponent就是一個xmpp組件,相當於一個擴充模組,可以接收訊息並處理訊息,也就是自己寫一些和xmpp相關的業務功能。
PushManager就是管理組件並串連到xmpp伺服器的一個類。
服務端啟動
public class App { public static void main( String[] args ) {PushManager.getInstance().init();//推送訊息PushServer ps = PushManager.getInstance().getPushServer("push");ps.start();JID client1 = new JID("1twja8e8yr@domain/1twja8e8yr");ps.subscription(client1);try {for (Integer i = 0; i< 200; i++) {ps.putPacket("推送訊息200:" + i.toString());Thread.sleep(1);} Thread.sleep(5000);} catch (InterruptedException e1) {e1.printStackTrace();}ps.stop(); System.out.println("go die"); }}
這段代碼類比了服務的啟動,同時為了簡化功能這裡直接添加了一個訂閱使用者。
用戶端
public class TestAnonymous {public static void main(String[] args) {AbstractXMPPConnection connection = SesseionHelper.newConn("192.168.149.214", 5223, "domain");try {connection.login();//匿名登入connection.addAsyncStanzaListener(new StanzaListener() {@Overridepublic void processPacket(Stanza packet) throws NotConnectedException {System.out.println((new Date()).toString()+ ":" + packet.toXML());}}, new StanzaFilter() {@Overridepublic boolean accept(Stanza stanza) {return stanza instanceof Message;}});} catch (XMPPException | SmackException | IOException e) {e.printStackTrace();}while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }}}
用戶端代碼啟動一個xmpp串連,然後登入到伺服器,同時訂閱訊息,將收到的訊息print出來。
整個過程就完成了。
註:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文連結!若您覺得這篇文章還不錯請點擊下右下角的推薦,非常感謝!http://www.cnblogs.com/5207