Android使用XMPP协议、Openfire服务器和Smack类库实现即时通信

来源:互联网 发布:网络规划设计师薪资 编辑:程序博客网 时间:2024/06/14 02:54

效果

   

介绍

XMPP(Extensible Messaging and Presence Protocal,可扩展通讯和表示协议)是一种基于XML的网络即时通信协议,它继承了在XML环境中灵活的发展性,因此,基于XMPP的应用具有超强的可扩展性。
XMPP的核心XML流传输协议的定义使得XMPP能够在一个比以往网络通信协议更规范的平台上,借助于XML易于解析和阅读的特性,使得XMPP协议能够更加漂亮。
XMPP中定义了三个角色:客户端,服务器和网关。通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,连接管理和信息的路由功能。网关承担着与异构通信系统的互联互通。基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML流。
简单来讲,它就是一个发送、接收、处理消息的协议,并使用XML作为消息传递的媒介。


Openfire基于XMPP协议,采用Java开发,可用于构建高效的即时通信服务器端,单台服务器可支持上万并发用户。由于是采用开放的XMPP协议,因此可以使用各种支持XMPP协议的IM客户端软件登录服务。

Smack是一个开源的、易于使用的XMPP客户端Java类库,提供了一套可扩展的API。
值得一提的是,Smack类库有一个基于Android平台的移植版本aSmack,曾经一度被广泛应用而今网上找的大多数例子也是基于aSmack进行开发的。但Smack类库在4.1.0版之后已经直接支持Android系统了,相应的,aSmack就不再进行更新和维护了,因此建议开发者们及早改用Smack类库。


实现
1.Openfire服务器的安装和配置
可参考:http://www.cnblogs.com/hoojo/archive/2012/05/17/2506769.html
需要注意的是,在第7项数据库设置里我选择的是标准数据库连接,对应使用的是我电脑上安装的MySQL数据库,如果你们电脑上没有安装MySQL,推荐使用一款叫phpStudy的一键集成PHP开发环境的软件,其中就包含了MySQL数据库。一键简单安装,无需任何配置。

在数据库驱动选项选择MySQL之后,第2、3个选项会自动帮你填写,我们还需要修改【数据库URL】选项中[host-name]和[database-name],分别是主机名和数据库名(记得修改时把[]去掉)。如果是在本机上调试,则主机名直接写localhost或127.0.0.1即可,数据库名需要在MySQL新建一个数据库后填上。然后,用户名和密码选项指的是MySQL数据库的登录用户和密码,默认登录名和密码都为root。


2.Smack类库的下载和引用
下载地址:http://download.csdn.net/detail/alfred_c/9297103

3.首先需要初始化与OpenFire服务器的连接配置,设置域名,主机号和端口号

private static void init(Context context) {// TODO 自动生成的方法存根mContext = context;try {XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();builder.setServiceName(SERVER_NAME);builder.setHost(SERVER_HOST);builder.setPort(SERVER_PORT);builder.setConnectTimeout(5000);// 设置连接OpenFire服务器超时时间builder.setDebuggerEnabled(false);// 设置是否启用调试模式,默认开启TLSUtils.acceptAllCertificates(builder);// 防止安全证书验证不通过的情况java.security.cert.CertificateExceptionconnection = new XMPPTCPConnection(builder.build());} catch (KeyManagementException e) {// TODO 自动生成的 catch 块Log.e("init", Log.getStackTraceString(e));} catch (NoSuchAlgorithmException e) {// TODO 自动生成的 catch 块Log.e("init", Log.getStackTraceString(e));}}
域名是你配置OpenFire时指定的服务器名称

主机号即IP地址

端口号为客户端连接到服务器的标准端口,一般固定为5222


4.一行代码完成新用户的注册。但因为可能出现诸如连接服务器超时,账号已存在等特殊情况,需要对异常进行捕获。由于涉及网络操作,所以需要另开线程进行,操作完成后再执行方法回调。

public void register(final String account, final String password, final OnStateListener listener) {new Thread(new Runnable() {@Overridepublic void run() {try {if (!connection.isConnected())connection.connect();AccountManager.getInstance(connection).createAccount(account, password);listener.onSuccess();} catch (NoResponseException e) {Log.e("register", Log.getStackTraceString(e));} catch (XMPPErrorException e) {if (e.toString().contains("conflict"))listener.onFail(mContext.getString(R.string.account_exist));Log.e("register", Log.getStackTraceString(e));} catch (NotConnectedException e) {Log.e("register", Log.getStackTraceString(e));} catch (SmackException e) {if (e.toString().contains("after 5000ms"))listener.onFail(mContext.getString(R.string.connect_timeout));Log.e("register", Log.getStackTraceString(e));} catch (IOException e) {Log.e("register", Log.getStackTraceString(e));} catch (XMPPException e) {Log.e("register", Log.getStackTraceString(e));}}}).start();}


5.登录操作,需要先判断用户是否已登录。

public void login(final String account, final String password, final OnStateListener listener) {new Thread(new Runnable() {@Overridepublic void run() {// TODO 自动生成的方法存根try {if (!connection.isConnected())connection.connect();if (connection.isAuthenticated()) {// 返回true则表示用户已登录成功,且与服务器保持连接listener.onFail(mContext.getString(R.string.logout_first));return;}connection.login(account, password);listener.onSuccess();if (chatManagerListener == null)chatManagerListener = new MyChatManagerListener();ChatManager.getInstanceFor(connection).addChatListener(chatManagerListener);} catch (SmackException e) {Log.e("login", Log.getStackTraceString(e));} catch (IOException e) {Log.e("login", Log.getStackTraceString(e));} catch (XMPPException e) {if (e.toString().contains("not-authorized")) {listener.onFail(mContext.getString(R.string.wrong_account_or_password));connection.disconnect();// 失败后还会保持原来的连接,必须先断开}}}}).start();}


6.通过关键字搜索用户。

public List<FriendBean> searchFriend(String key) {List<FriendBean> friends = new ArrayList<FriendBean>();try {UserSearchManager usManager = new UserSearchManager(connection);Form searchForm = usManager.getSearchForm("search." + connection.getServiceName());// 在最新版本的Openfire// 3.10.3中调用时总是会报remote-server-not-found,如果出现,请降低OpenFire版本使用Form answerForm = searchForm.createAnswerForm();answerForm.setAnswer("Username", true);answerForm.setAnswer("search", key);ReportedData data = usManager.getSearchResults(answerForm, "search." + connection.getServiceName());List<Row> rows = data.getRows();for (int i = 0; i < rows.size(); i++) {Row row = rows.get(i);FriendBean friend = new FriendBean();friend.setUserName(row.getValues("Username").get(0));friends.add(friend);}} catch (NoResponseException e) {Log.e("searchFriend", Log.getStackTraceString(e));} catch (XMPPErrorException e) {Log.e("searchFriend", Log.getStackTraceString(e));} catch (NotConnectedException e) {Log.e("searchFriend", Log.getStackTraceString(e));}return friends;}


7.下面是实现点对点即时通信最关键的代码了。

a.要实现发送消息,需要通过userJID构建一个会话,userJID的组成是“用户名”+“@”+“域名”:

String userJID = targetName + "@" + connection.getServiceName();Chat chat = ChatManager.getInstanceFor(connection).createChat(userJID);
   然后调用chat.sendMessage(text))即可发送文本消息了,我这里是构建了一个消息实体,再转换为Json字符串进行发送:

Gson gson = new Gson();String msgJson = gson.toJson(msg);try {chat.sendMessage(msgJson);listener.onSuccess();} catch (NotConnectedException e) {Log.e("sendMsg", Log.getStackTraceString(e));}

b.而要实现接受消息,则需要继承ChatManagerListener并重写chatCreated方法:

public class MyChatManagerListener implements ChatManagerListener{@Overridepublic void chatCreated(Chat chat, boolean arg1) {// TODO 自动生成的方法存根chat.addMessageListener(new ChatMessageListener(){@Overridepublic void processMessage(Chat chat, Message msg) {// TODO 自动生成的方法存根String body = msg.getBody();//取得消息主体try {JSONObject jsonObj = new JSONObject(body);String from = jsonObj.getString("from");String to = jsonObj.getString("to");String content = jsonObj.getString("content");MsgBean newMsg = new MsgBean();newMsg.setFrom(from);newMsg.setTo(to);newMsg.setContent(content);EventBus.getDefault().post(new NewMsgEvent(newMsg));//发布事件,通知聊天界面进行消息更新} catch (JSONException e) {// TODO 自动生成的 catch 块e.printStackTrace();}}});}}
    取得消息主体之后重新解析成Json,然后取得发送人、接收人和消息内容并重新构建一个消息实体,传递到聊天界面进行更新。


源码

http://download.csdn.net/detail/alfred_c/9297311

参考

http://my.oschina.net/cuitongliang/blog/194885?fromerr=GblfTBic

1 0