效果
介绍
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.CertificateException
connection = 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() {
@Override
public 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() {
@Override
public 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 searchFriend(String key) {
List friends = new ArrayList();
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 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{
@Override
public void chatCreated(Chat chat, boolean arg1) {
// TODO 自动生成的方法存根
chat.addMessageListener(new ChatMessageListener(){
@Override
public 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