Openfire
采用Java開發(fā),開源的實時協(xié)作(RTC)服務器基于XMPP(Jabber)協(xié)議。Openfire安裝和使用都非常簡單,并利用Web進行管理。單臺服務器可支持上萬并發(fā)用戶。
好友界面
功能界面
聊天界面
服務端可以用本服務器測試,地址:www.ithtw.com。如果想自己搭可在本博客其它XMPP文章找到相關搭建資料。
實現(xiàn)了用戶注冊,登錄,添加好友,聊天核心功能。可正常聊天!
核心通訊代碼:
package com.lee.xqq.control;import java.io.ByteArrayInputStream;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;import java.util.List;import org.jivesoftware.smack.Connection;import org.jivesoftware.smack.ConnectionConfiguration;import org.jivesoftware.smack.ConnectionCreationListener;import org.jivesoftware.smack.ConnectionListener;import org.jivesoftware.smack.PacketCollector;import org.jivesoftware.smack.PacketListener;import org.jivesoftware.smack.Roster;import org.jivesoftware.smack.RosterEntry;import org.jivesoftware.smack.RosterGroup;import org.jivesoftware.smack.SmackConfiguration;import org.jivesoftware.smack.XMPPConnection;import org.jivesoftware.smack.XMPPException;import org.jivesoftware.smack.filter.AndFilter;import org.jivesoftware.smack.filter.PacketFilter;import org.jivesoftware.smack.filter.PacketIDFilter;import org.jivesoftware.smack.filter.PacketTypeFilter;import org.jivesoftware.smack.packet.IQ;import org.jivesoftware.smack.packet.Message;import org.jivesoftware.smack.packet.Packet;import org.jivesoftware.smack.packet.Presence;import org.jivesoftware.smack.packet.Presence.Type;import org.jivesoftware.smack.packet.Registration;import org.jivesoftware.smack.provider.PrivacyProvider;import org.jivesoftware.smack.provider.ProviderManager;import org.jivesoftware.smack.util.StringUtils;import org.jivesoftware.smackx.Form;import org.jivesoftware.smackx.GroupChatInvitation;import org.jivesoftware.smackx.PrivateDataManager;import org.jivesoftware.smackx.ReportedData;import org.jivesoftware.smackx.ReportedData.Row;import org.jivesoftware.smackx.bytestreams.socks5.provider.BytestreamsProvider;import org.jivesoftware.smackx.packet.ChatStateExtension;import org.jivesoftware.smackx.packet.LastActivity;import org.jivesoftware.smackx.packet.OfflineMessageInfo;import org.jivesoftware.smackx.packet.OfflineMessageRequest;import org.jivesoftware.smackx.packet.SharedGroupsInfo;import org.jivesoftware.smackx.packet.VCard;import org.jivesoftware.smackx.provider.AdHocCommandDataProvider;import org.jivesoftware.smackx.provider.DataFormProvider;import org.jivesoftware.smackx.provider.DelayInformationProvider;import org.jivesoftware.smackx.provider.DiscoverInfoProvider;import org.jivesoftware.smackx.provider.DiscoverItemsProvider;import org.jivesoftware.smackx.provider.MUCAdminProvider;import org.jivesoftware.smackx.provider.MUCOwnerProvider;import org.jivesoftware.smackx.provider.MUCUserProvider;import org.jivesoftware.smackx.provider.MessageEventProvider;import org.jivesoftware.smackx.provider.MultipleAddressesProvider;import org.jivesoftware.smackx.provider.RosterExchangeProvider;import org.jivesoftware.smackx.provider.StreamInitiationProvider;import org.jivesoftware.smackx.provider.VCardProvider;import org.jivesoftware.smackx.provider.XHTMLExtensionProvider;import org.jivesoftware.smackx.search.UserSearch;import org.jivesoftware.smackx.search.UserSearchManager;import android.graphics.drawable.BitmapDrawable;import android.graphics.drawable.Drawable;import com.lee.xqq.ChatActivity;import com.lee.xqq.MainActivity;import com.lee.xqq.SplashActivity;import com.lee.xqq.base.BroadSender;import com.lee.xqq.base.MLog;import com.lee.xqq.item.XUserItem;import com.lee.xqq.net.HttpResult;import com.lee.xqq.net.HttpUtil;import com.lee.xqq.util.CachedThreadPool;/** * 所有的數(shù)據(jù)控制,邏輯可以在這里實現(xiàn) * */public class DataUtils implements ConnectionListener, ConnectionCreationListener, PacketListener { private static DataUtils instance; private Connection connection; private static String host; private static int port; private String user; private DataUtils() { } public void setHost(String host, int port) { DataUtils.host = host; DataUtils.port = port; } public static DataUtils getInstance() { if (instance == null) instance = new DataUtils(); return instance; } public Connection getConnection() { return connection; } public String getUser() { return user; } public void requestBaidu() { CachedThreadPool.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub HttpUtil httpUtil = HttpUtil.requestText( "http://www.baidu.com", "GET"); HttpResult result = httpUtil.getResponse(); if (result.getCode() == 200) { // 向界面發(fā)送接收廣播 } } }); } public void initXMPP() { CachedThreadPool.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub connectXmppServer(); } }); } private void connectXmppServer() { ConnectionConfiguration config = new ConnectionConfiguration(host, port); /** 是否啟用安全驗證 */ config.setSASLAuthenticationEnabled(false); config.setReconnectionAllowed(true);// 斷線重連 /** 是否啟用調試 */ // config.setDebuggerEnabled(true); /** 創(chuàng)建connection鏈接 */ try { Connection.addConnectionCreationListener(this); configure(ProviderManager.getInstance()); connection = new XMPPConnection(config); /** 建立連接 */ connection.connect(); connection.addConnectionListener(this); PacketTypeFilter filter = new PacketTypeFilter(Packet.class); connection.addPacketListener(this, filter); } catch (XMPPException e) { e.printStackTrace(); MLog.makeText("服務器連接失敗"); } } /** * Android讀不到/META-INF下的配置文件,需要手工配置。 * * @param pm */ private void configure(ProviderManager pm) { // Private Data Storage pm.addIQProvider("query", "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider()); // Time try { pm.addIQProvider("query", "jabber:iq:time", Class.forName("org.jivesoftware.smackx.packet.Time")); } catch (ClassNotFoundException e) { e.printStackTrace(); } // Roster Exchange pm.addExtensionProvider("x", "jabber:x:roster", new RosterExchangeProvider()); // Message Events pm.addExtensionProvider("x", "jabber:x:event", new MessageEventProvider()); // Chat State pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider()); // XHTML pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider()); // Group Chat Invitations pm.addExtensionProvider("x", "jabber:x:conference", new GroupChatInvitation.Provider()); // Service Discovery # Items pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); // Service Discovery # Info pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); // Data Forms pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider()); // MUC User pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new MUCUserProvider()); // MUC Admin pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); // MUC Owner pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); // Delayed Delivery pm.addExtensionProvider("x", "jabber:x:delay", new DelayInformationProvider()); // Version try { pm.addIQProvider("query", "jabber:iq:version", Class.forName("org.jivesoftware.smackx.packet.Version")); } catch (ClassNotFoundException e) { // Not sure what's happening here. e.printStackTrace(); } // VCard pm.addIQProvider("vCard", "vcard-temp", new VCardProvider()); // Offline Message Requests pm.addIQProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider()); // Offline Message Indicator pm.addExtensionProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider()); // Last Activity pm.addIQProvider("query", "jabber:iq:last", new LastActivity.Provider()); // User Search pm.addIQProvider("query", "jabber:iq:search", new UserSearch.Provider()); // SharedGroupsInfo pm.addIQProvider("sharedgroup", "http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider()); // JEP-33: Extended Stanza Addressing pm.addExtensionProvider("addresses", "http://jabber.org/protocol/address", new MultipleAddressesProvider()); // FileTransfer pm.addIQProvider("si", "http://jabber.org/protocol/si", new StreamInitiationProvider()); pm.addIQProvider("query", "http://jabber.org/protocol/bytestreams", new BytestreamsProvider()); // Privacy pm.addIQProvider("query", "jabber:iq:privacy", new PrivacyProvider()); pm.addIQProvider("command", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider()); pm.addExtensionProvider("malformed-action", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.MalformedActionError()); pm.addExtensionProvider("bad-locale", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadLocaleError()); pm.addExtensionProvider("bad-payload", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadPayloadError()); pm.addExtensionProvider("bad-sessionid", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.BadSessionIDError()); pm.addExtensionProvider("session-expired", "http://jabber.org/protocol/commands", new AdHocCommandDataProvider.SessionExpiredError()); } public boolean register(String account, String password) { Registration reg = new Registration(); reg.setType(IQ.Type.SET); reg.setTo(connection.getServiceName()); reg.setUsername(account);// 注意這里createAccount注冊時,參數(shù)是username,不是jid,是“@”前面的部分。 reg.setPassword(password); reg.addAttribute("android", "geolo_createUser_android");// 這邊addAttribute不能為空,否則出錯。所以做個標志是android手機創(chuàng)建的吧?。。。?! PacketFilter filter = new AndFilter(new PacketIDFilter( reg.getPacketID()), new PacketTypeFilter(IQ.class)); PacketCollector collector = connection.createPacketCollector(filter); connection.sendPacket(reg); IQ result = (IQ) collector.nextResult(SmackConfiguration .getPacketReplyTimeout()); if (result == null) { MLog.makeText("連接超時"); } else if (result.getType() == IQ.Type.RESULT) { MLog.makeText("注冊成功"); return true; } else { MLog.makeText(result.getError().toString()); } return false; } public boolean login(String userName, String userPwd) { try { connection.login(userName, userPwd); user = userName + "@" + connection.getServiceName(); return true; } catch (Exception e) { e.printStackTrace(); } MLog.makeText("登錄失敗"); return false; } public void changePassword(String pwd) { try { connection.getAccountManager().changePassword(pwd); } catch (Exception e) { e.printStackTrace(); } } /** * 更改用戶狀態(tài) */ public void setPresence(int code) { Presence presence = null; switch (code) { case 0: presence = new Presence(Presence.Type.available); connection.sendPacket(presence); MLog.makeText("設置在線"); break; case 1: presence = new Presence(Presence.Type.available); presence.setMode(Presence.Mode.chat); connection.sendPacket(presence); MLog.makeText("設置Q我吧"); break; case 2: presence = new Presence(Presence.Type.available); presence.setMode(Presence.Mode.dnd); connection.sendPacket(presence); MLog.makeText("設置忙碌"); break; case 3: presence = new Presence(Presence.Type.available); presence.setMode(Presence.Mode.away); connection.sendPacket(presence); MLog.makeText("設置離開"); break; case 4: Roster roster = connection.getRoster(); Collection<RosterEntry> entries = roster.getEntries(); for (RosterEntry entry : entries) { presence = new Presence(Presence.Type.unavailable); presence.setPacketID(Packet.ID_NOT_AVAILABLE); presence.setFrom(connection.getUser()); presence.setTo(entry.getUser()); connection.sendPacket(presence); } // 向同一用戶的其他客戶端發(fā)送隱身狀態(tài) presence = new Presence(Presence.Type.unavailable); presence.setPacketID(Packet.ID_NOT_AVAILABLE); presence.setFrom(connection.getUser()); presence.setTo(StringUtils.parseBareAddress(connection.getUser())); connection.sendPacket(presence); MLog.makeText("設置隱身"); break; case 5: presence = new Presence(Presence.Type.unavailable); connection.sendPacket(presence); MLog.makeText("設置離線"); break; default: break; } } public void logoutAccount() { try { connection.getAccountManager().deleteAccount(); } catch (Exception e) { e.printStackTrace(); } } public Collection<RosterGroup> getGroups() { return connection.getRoster().getGroups(); } public List<RosterEntry> getEntriesByGroup(String groupName) { List<RosterEntry> Entrieslist = new ArrayList<RosterEntry>(); RosterGroup rosterGroup = connection.getRoster().getGroup(groupName); Collection<RosterEntry> rosterEntry = rosterGroup.getEntries(); Iterator<RosterEntry> i = rosterEntry.iterator(); while (i.hasNext()) { Entrieslist.add(i.next()); } return Entrieslist; } public Drawable getUserImage(String user) { ByteArrayInputStream bais = null; try { VCard vcard = new VCard(); // 加入這句代碼,解決No VCard for ProviderManager.getInstance().addIQProvider("vCard", "vcard-temp", new org.jivesoftware.smackx.provider.VCardProvider()); if (user == "" || user == null || user.trim().length() <= 0) { return null; } vcard.load(connection, user + "@" + connection.getServiceName()); if (vcard == null || vcard.getAvatar() == null) return null; bais = new ByteArrayInputStream(vcard.getAvatar()); BitmapDrawable drawable = new BitmapDrawable(bais); bais.close(); return drawable; } catch (Exception e) { e.printStackTrace(); } return null; } public List<RosterEntry> getAllEntries() { List<RosterEntry> Entrieslist = new ArrayList<RosterEntry>(); Collection<RosterEntry> rosterEntry = connection.getRoster() .getEntries(); Iterator<RosterEntry> i = rosterEntry.iterator(); while (i.hasNext()) { Entrieslist.add(i.next()); } return Entrieslist; } public boolean addGroup(String groupName) { try { connection.getRoster().createGroup(groupName); MLog.makeText("創(chuàng)建成功"); return true; } catch (Exception e) { e.printStackTrace(); } return false; } /** * 添加好友 無分組 * * @param userName * @param name * @return */ public boolean addUser(String userName, String name) { try { String account = userName + "@" + connection.getServiceName(); connection.getRoster().createEntry(account, name, null); return true; } catch (Exception e) { e.printStackTrace(); } return false; } public boolean addUserWithName(String userName, String name, Presence.Type type) { return addUserWithName(userName, name, XMsgConst.X_DEFAULT_GROUP, type); } /** * 添加好友 有分組 * * @param userName * @param name * @param groupName * @return */ public boolean addUserWithName(String userName, String name, String groupName, Presence.Type type) { try { Presence subscription = new Presence(type); subscription.setTo(userName); userName += "@" + connection.getServiceName(); connection.sendPacket(subscription); if (type == Type.subscribed) connection.getRoster().createEntry(userName, name, new String[] { groupName }); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean removeUser(String userName) { try { RosterEntry entry = null; if (userName.contains("@")) entry = connection.getRoster().getEntry(userName); else entry = connection.getRoster().getEntry( userName + "@" + connection.getServiceName()); if (entry == null) entry = connection.getRoster().getEntry(userName); connection.getRoster().removeEntry(entry); return true; } catch (Exception e) { e.printStackTrace(); } return false; } public List<XUserItem> searchUsers(String userName) { List<XUserItem> results = new ArrayList<XUserItem>(); try { UserSearchManager usm = new UserSearchManager(connection); String serverDomain = "search." + connection.getServiceName(); Form searchForm = usm.getSearchForm(serverDomain); Form answerForm = searchForm.createAnswerForm(); answerForm.setAnswer("Username", true); answerForm.setAnswer("search", userName); ReportedData data = usm.getSearchResults(answerForm, serverDomain); Iterator<Row> it = data.getRows(); Row row = null; XUserItem user = null; while (it.hasNext()) { row = it.next(); String uName = row.getValues("Username").next().toString(); String Name = row.getValues("Name").next().toString(); String Email = row.getValues("Email").next().toString(); user = new XUserItem(uName, Name, Email); results.add(user); } return results; } catch (XMPPException e) { // TODO Auto-generated catch block e.printStackTrace(); } return results; } public void sendMessage(String toJID, String message) { // TODO Auto-generated method stub final Message newMessage = new Message(toJID, Message.Type.chat); newMessage.setBody(message); connection.sendPacket(newMessage); } @Override public void connectionClosed() { // TODO Auto-generated method stub MLog.makeText("連接已關閉"); } @Override public void connectionClosedOnError(Exception arg0) { // TODO Auto-generated method stub MLog.makeText("連接關閉異常"); arg0.printStackTrace(); } @Override public void reconnectingIn(int arg0) { // TODO Auto-generated method stub MLog.makeText("reconnectingIn:" + arg0); } @Override public void reconnectionFailed(Exception arg0) { // TODO Auto-generated method stub MLog.makeText("連接失敗"); arg0.printStackTrace(); } @Override public void reconnectionSuccessful() { // TODO Auto-generated method stub MLog.makeText("重連成功"); } @Override public void connectionCreated(Connection arg0) { // TODO Auto-generated method stub BroadSender.sendBroadcast(SplashActivity.class); } @Override public void processPacket(Packet packet) { // TODO Auto-generated method stub if (packet instanceof Message) { Message msg = (Message) packet; CacheUtils.getInstance().addMessage(msg); BroadSender.sendBroadcast(ChatActivity.class, "action", XMsgConst.X_PACKET_TYPE_MESSAGE); } else if (packet instanceof Presence) { Presence presence = (Presence) packet; String from = presence.getFrom();// 發(fā)送方 String to = presence.getTo();// 接收方 Type type = presence.getType(); int typeValue = 0; if (type.equals(Presence.Type.subscribe)) { // 好友申請 typeValue = XMsgConst.X_PRESENCE_SUBSCRIBE; } else if (type.equals(Presence.Type.subscribed)) { // 同意添加好友 typeValue = XMsgConst.X_PRESENCE_SUBSCRIBED; } else if (type.equals(Presence.Type.unsubscribe)) { // 拒絕添加好友 typeValue = XMsgConst.X_PRESENCE_UNSUBSCRIBE; } else if (type.equals(Presence.Type.unsubscribed)) { // 刪除好友 typeValue = XMsgConst.X_PRESENCE_UNSUBSCRIBED; } else if (type.equals(Presence.Type.unavailable)) { // 好友下線 typeValue = XMsgConst.X_PRESENCE_UNAVAILABLE; from = processFrom(from); CacheUtils.getInstance().putPresence(from, presence); } else { // 好友上線 typeValue = XMsgConst.X_PRESENCE_ONLINE; from = processFrom(from); CacheUtils.getInstance().putPresence(from, presence); } String[] params = new String[] { "action", XMsgConst.X_PACKET_TYPE_PRESENCE, "type", String.valueOf(typeValue), "from", from, "to", to }; BroadSender.sendBroadcast(MainActivity.class, params); } } /** * 去掉多余的服務器字符串 * * @param from * @return */ private String processFrom(String from) { int endPos = from.lastIndexOf("/"); if (endPos != -1) from = from.substring(0, endPos); return from; }}