xmpp smack类库实现聊天功能

[1]连接、登陆及账户操作需求:
基于XMPP的IM工具,需实现和gtalk实现通信,需实现同spark的通信,需架设服务器,实现同自身客户端的通信,传文件,视频聊天

写完未实现需要改进的地方:离线消息,离线文件
一、XMPP

XMPP : The Extensible Messaging and Presence Protocol
中文全称:可扩展通讯和表示协议
简介:
可扩展通讯和表示协议 (XMPP) 可用于服务类实时通讯、表示和需求 - 响应服务中的 XML 数据元流式传输。XMPP 以 Jabber 协议为基础,而 Jabber 是即时通讯中常用的开放式协议。

二、Smack

Smack是一个开源,易于使用的XMPP(jabber)客户端类库。 

Smack API, 是一个 Java 的XMPP Client Library,也是由Jive Software开发。 优点:编程简单。 缺点:API并非为大量并发用户设计,每个客户要1个线程,占用资源大,1台机器只能模拟有限(数千个)客户.
smack是一个用 java 写的XMPP客户端代码库, 是 spark 的核心.

二、连接及断开
基本方法

XMPPConnection connection = new XMPPConnection(“gmail.com”);
connection.connect();
实现的方法

[java] view plaincopyprint?

[list=1]
[]public static XMPPConnection getConnection(String domain) throws XMPPException {
[
]
[] XMPPConnection connection = new XMPPConnection(domain);
[
] connection.connect();
[] return connection;
[
]}
[]
[
]
[]public static XMPPConnection getConnection(String domain,int port) throws XMPPException {
[
] ConnectionConfiguration config = new ConnectionConfiguration(domain,port);
[] XMPPConnection connection = new XMPPConnection(config);
[
] connection.connect();
[] return connection;
[
]}
[/list]
断开

 connection.disconnect();

四、登陆

connection.login(“javatest2011@gmail.com”, “*****”);

五、账户操作

可以对账户进行基本操作,包括注册,注销,修改密码
[c-sharp] view plaincopyprint?

[list=1]
[]/**
[
] * 注册用户
[] * @param connection
[
] * @param regUserName
[] * @param regUserPwd
[
] * @return
[] /
[
] public static boolean createAccount(XMPPConnection connection,String regUserName,String regUserPwd)
[
] {
[] try {
[
] connection.getAccountManager().createAccount(regUserName, regUserPwd);
[] return true;
[
] } catch (Exception e) {
[] return false;
[
] }
[] }
[
]
[] /**
[
] * 删除当前用户
[] * @param connection
[
] * @return
[] /
[
] public static boolean deleteAccount(XMPPConnection connection)
[
] {
[] try {
[
] connection.getAccountManager().deleteAccount();
[]
[
] return true;
[] } catch (Exception e) {
[
] return false;
[] }
[
] }
[]
[
] /**
[] * 删除修改密码
[
] * @param connection
[] * @return
[
] /
[
] public static boolean changePassword(XMPPConnection connection,String pwd)
[] {
[
] try {
[] connection.getAccountManager().changePassword(pwd);
[
]
[] return true;
[
] } catch (Exception e) {
[] return false;
[
] }
[*] }
[/list]

[2]会话、消息监听、字体表情和聊天窗口控制上一篇是连接,登陆登出和账户管理
继续
连接之后,拿到了connection,通过它可以搞定会话

  1.   建立一个会话
    

[java] view plaincopyprint?

[list=1]
[]MessageListener msgListener
[
] = new MessageListener()
[] {
[
] public void processMessage(Chat chat, Message message)
[] {
[
]
[] if (message != null && message.getBody() != null)
[
] {
[] System.out.println(“收到消息:” + message.getBody());
[
] // 可以在这进行针对这个用户消息的处理,但是这里我没做操作,看后边聊天窗口的控制
[] }
[
]
[] }
[
] };
[]Chat chat = Client.getConnection().getChatManager()
[
] .createChat(userName, msgListener);
[/list]

通过会话发送消息
两个方法,一个直接发送一条文本,一个发送一个Message对象,可包含一些信息,一般使用后者,因为需要包装字体等信息
[java] view plaincopyprint?

[list=1]
[]public static void sendMessage(Chat chat,String message) throws XMPPException {
[
] chat.sendMessage(message);
[] }
[
]
[] public static void sendMessage(Chat chat,Message message) throws XMPPException {
[
] chat.sendMessage(message);
[*] }
[/list]

  1.   消息监听
    

每个connection的chatManager可以设置一个消息监听器,因为IM必须实现他人对你发起会话也会弹出窗口,即自己可以主动发起会话,也可以接收他人发起的会话
[java] view plaincopyprint?

[list=1]
[]ChatManager manager = Client.getConnection().getChatManager();
[
] manager.addChatListener(new ChatManagerListener() {
[] public void chatCreated(Chat chat, boolean arg1) {
[
] chat.addMessageListener(new MessageListener() {
[] public void processMessage(Chat arg0, Message message) {
[
] //若是聊天窗口已存在,将消息转往目前窗口
[] //若是窗口不存在,开新的窗口并注册
[
]
[] }
[
] });
[] }
[
] });
[/list]

其实窗口的管理是使用线程的,来一个新的会话,起线程
3. 字体表情
在这里实现字体和表情是使用自身开发IM之间的实现。

字体实现思想:
在发送消息的同时,将字体内容作为附加信息发送,接收方接收到根据字体信息进行处理后显示

实现:使用Message对消息进行封装
[java] view plaincopyprint?

[list=1]
[]Message msg = new Message();
[
] msg.setProperty(“size”, size);
[] msg.setProperty(“kind”, kind);
[
] msg.setProperty(“bold”, bold);
[] msg.setProperty(“italic”, italic);
[
] msg.setProperty(“underline”, underline);
[] msg.setProperty(“color”, color);
[
] msg.setBody(getSendInfo());//真正的消息
[*] chat.sendMessage(msg);
[/list]

接收方先获取设置信息后展示

   展示的控件: JTextPanereceiveText =[b]new[/b] JTextPane();

[java] view plaincopyprint?

[list=1]
[]Style style = receiveText.addStyle(“font”, null);
[
] StyleConstants.setFontSize(style, size);
[] StyleConstants.setFontFamily(style, kind);
[
] StyleConstants.setBold(style, bold);
[] StyleConstants.setItalic(style, italic);
[
] StyleConstants.setUnderline(style, underline);
[*] StyleConstants.setForeground(style, color);
[/list]

表情:

   实现机制是客户端本身存有一套表情图片,在选中时,将表情编号加入到消息中,实际发送出去的只是文本,拿到后解析字符串,将编号转为具体表情展示

  具体就不写了
  1. 聊天窗口控制

所谓控制,主要是控制唯一性

无论是你发起一个会话开启一个窗口,还是对方给你发送会话开启,你与对方之间有且仅有一个窗口,之后任何消息都在这个窗口中处理

思想:单例类,持有一个

//现有的聊天窗口

[b]publicstatic[/b] TreeMap<String,TelFrame>[i]currentChat[/i] =[b]new[/b] TreeMap<String,TelFrame>();

其实应该用concurrentHashMap可能会更好,不顾我这边分派的主线程只有一个,不涉及多个线程并发的问题,所以用了Treemap,汗一个,貌似应该hashmap,当时考虑不同

然后,在接收消息的时候根据发消息的用户,判定窗口是否存在,存在,转发消息到该窗口,不存在,建立新的窗口

若是关闭窗口,注销之

[java] view plaincopyprint?

[list=1]
[]//注册聊天室
[
] public static void registerChat(String userName,TelFrame chatroom)
[] {
[
] //System.out.println(“注册:”+userName);
[] currentChat.put(userName, chatroom);
[
] }
[] //注销注册
[
] public static void removeChat(String userName)
[] {
[
] System.out.println(“用户注销聊天室:”+userName);
[] currentChat.remove(userName);
[
] }
[] //查看是否已有
[
] public static boolean isChatExist(String userName)
[] {
[
]
[] return currentChat.containsKey(userName);
[
] }
[] //获取对象
[
] public static TelFrame getChatRoom(String userName)
[] {
[
] return currentChat.get(userName);
[] }
[
]
[*] 下一篇,主要是用户列表,头像,分组及管理的
[/list]
[3]用户列表,头像,组操作,用户操作转载请注明出处:http://blog.csdn.net/wklken/archive/2011/06/01/6460112.aspx
上一篇主要是会话的管理
继续,这是显示用户列表方面的

  1.   用户列表
    

Smack主要使用Roster进行列表管理的
connection.getRoster();
[java] view plaincopyprint?

[list=1]
[]/**
[
] * 返回所有组信息
[] *
[
] * @return List(RosterGroup)
[] /
[
] public static List getGroups(Roster roster) {
[
] List groupsList = new ArrayList();
[] Collection rosterGroup = roster.getGroups();
[
] Iterator i = rosterGroup.iterator();
[] while (i.hasNext())
[
] groupsList.add(i.next());
[] return groupsList;
[
] }
[]
[
] /**
[] * 返回相应(groupName)组里的所有用户
[
] *
[] * @return List(RosterEntry)
[
] /
[
] public static List getEntriesByGroup(Roster roster,
[] String groupName) {
[
] List EntriesList = new ArrayList();
[] RosterGroup rosterGroup = roster.getGroup(groupName);
[
] Collection rosterEntry = rosterGroup.getEntries();
[] Iterator i = rosterEntry.iterator();
[
] while (i.hasNext())
[] EntriesList.add(i.next());
[
] return EntriesList;
[] }
[
]
[] /**
[
] * 返回所有用户信息
[] *
[
] * @return List(RosterEntry)
[] /
[
] public static List getAllEntries(Roster roster) {
[
] List EntriesList = new ArrayList();
[] Collection rosterEntry = roster.getEntries();
[
] Iterator i = rosterEntry.iterator();
[] while (i.hasNext())
[
] EntriesList.add(i.next());
[] return EntriesList;
[
] }
[/list]

这里注意下,与gtalk通讯,貌似gtalk是没有分组的,汗,所以使用第三个方法直接取

当然,还要处理,若是刚注册用户,一个组都没有的,需要默认两个组,我的好友及黑名单
黑名单的消息,一律杀掉,不会接受处理
2. 用户头像的获取
使用VCard,很强大,具体自己看API吧
可以看看VCard传回来XML的组成,含有很多信息的

[java] view plaincopyprint?

[list=1]
[]/**
[
] * 获取用户的vcard信息
[] * @param connection
[
] * @param user
[] * @return
[
] * @throws XMPPException
[] /
[
] public static VCard getUserVCard(XMPPConnection connection, String user) throws XMPPException
[
] {
[] VCard vcard = new VCard();
[
] vcard.load(connection, user);
[]
[
] return vcard;
[] }
[
]
[]获取头像使用
[
] /**
[] * 获取用户头像信息
[
] /
[
] public static ImageIcon getUserImage(XMPPConnection connection, String user) {
[] ImageIcon ic = null;
[
] try {
[] System.out.println("获取用户头像信息: "+user);
[
] VCard vcard = new VCard();
[] vcard.load(connection, user);
[
]
[] if(vcard == null || vcard.getAvatar() == null)
[
] {
[] return null;
[
] }
[] ByteArrayInputStream bais = new ByteArrayInputStream(
[
] vcard.getAvatar());
[] Image image = ImageIO.read(bais);
[
]
[]
[
] ic = new ImageIcon(image);
[] System.out.println(“图片大小:”+ic.getIconHeight()+" "+ic.getIconWidth());
[
]
[] } catch (Exception e) {
[
] e.printStackTrace();
[] }
[
] return ic;
[*] }
[/list]

  1.   组操作和用户分组操作
    

主要是建立删除分组,用户添加到分组等操作

[c-sharp] view plaincopyprint?

[list=1]
[]/**
[
] * 添加一个组
[] /
[
] public static boolean addGroup(Roster roster,String groupName)
[
] {
[] try {
[
] roster.createGroup(groupName);
[] return true;
[
] } catch (Exception e) {
[] e.printStackTrace();
[
] return false;
[] }
[
] }
[]
[
] /**
[] * 删除一个组
[
] /
[
] public static boolean removeGroup(Roster roster,String groupName)
[] {
[
] return false;
[] }
[
]
[] /**
[
] * 添加一个好友 无分组
[] /
[
] public static boolean addUser(Roster roster,String userName,String name)
[
] {
[] try {
[
] roster.createEntry(userName, name, null);
[] return true;
[
] } catch (Exception e) {
[] e.printStackTrace();
[
] return false;
[] }
[
] }
[] /**
[
] * 添加一个好友到分组
[] * @param roster
[
] * @param userName
[] * @param name
[
] * @return
[] /
[
] public static boolean addUser(Roster roster,String userName,String name,String groupName)
[
] {
[] try {
[
] roster.createEntry(userName, name,new String{ groupName});
[] return true;
[
] } catch (Exception e) {
[] e.printStackTrace();
[
] return false;
[] }
[
] }
[]
[
] /**
[] * 删除一个好友
[
] * @param roster
[] * @param userName
[
] * @return
[] /
[
] public static boolean removeUser(Roster roster,String userName)
[
] {
[] try {
[
]
[] if(userName.contains(“@”))
[
] {
[] userName = userName.split(“@”)[0];
[
] }
[] RosterEntry entry = roster.getEntry(userName);
[
] System.out.println(“删除好友:”+userName);
[] System.out.println("User: "+(roster.getEntry(userName) == null));
[
] roster.removeEntry(entry);
[]
[
] return true;
[] } catch (Exception e) {
[
] e.printStackTrace();
[] return false;
[
] }
[]
[
] }
[/list]

  1. 用户查询
    

本来是用户操作的,分组和增删在3里讲了,这里主要是查询操作
查询用户
[java] view plaincopyprint?

[list=1]
[]public static List searchUsers(XMPPConnection connection,String serverDomain,String userName) throws XMPPException
[
] {
[] List results = new ArrayList();
[
] System.out.println(“查询开始…”+connection.getHost()+connection.getServiceName());
[]
[
] UserSearchManager usm = new UserSearchManager(connection);
[]
[
]
[] Form searchForm = usm.getSearchForm(serverDomain);
[
] Form answerForm = searchForm.createAnswerForm();
[] answerForm.setAnswer(“Username”, true);
[
] answerForm.setAnswer(“search”, userName);
[] ReportedData data = usm.getSearchResults(answerForm, serverDomain);
[
]
[] Iterator it = data.getRows();
[
] Row row = null;
[] UserBean user = null;
[
] while(it.hasNext())
[] {
[
] user = new UserBean();
[] row = it.next();
[
] user.setUserName(row.getValues(“Username”).next().toString());
[] user.setName(row.getValues(“Name”).next().toString());
[
] user.setEmail(row.getValues(“Email”).next().toString());
[] System.out.println(row.getValues(“Username”).next());
[
] System.out.println(row.getValues(“Name”).next());
[] System.out.println(row.getValues(“Email”).next());
[
] results.add(user);
[] //若存在,则有返回,UserName一定非空,其他两个若是有设,一定非空
[
] }
[]
[
] return results;
[*] }
[/list]

以上查询貌似是多字段查询,即用户多个属性中某一个符合即作为查询结果
实际是可以实现根据某一特定字段查询的,如用户名,或昵称,这里笼统了,若需扩展去查看下API重写下

下一篇,状态,心情和头像修改
[4]状态,心情,头像更改这里写完,最基本的IM功能也就完了,
还剩下个发送接收文件,离线消息扩展等等

呵呵,三天时间,看的不是很深入,欢迎大家补充呀

  1.   修改自身状态
    

包括上线,隐身,对某人隐身,对某人上线
[java] view plaincopyprint?

[list=1]
[]public static void updateStateToAvailable(XMPPConnection connection)
[
] {
[] Presence presence = new Presence(Presence.Type.available);
[
] connection.sendPacket(presence);
[] }
[
]
[] public static void updateStateToUnAvailable(XMPPConnection connection)
[
] {
[] Presence presence = new Presence(Presence.Type.unavailable);
[
] connection.sendPacket(presence);
[] }
[
]
[] public static void updateStateToUnAvailableToSomeone(XMPPConnection connection,String userName)
[
] {
[] Presence presence = new Presence(Presence.Type.unavailable);
[
] presence.setTo(userName);
[] connection.sendPacket(presence);
[
] }
[] public static void updateStateToAvailableToSomeone(XMPPConnection connection,String userName)
[
] {
[] Presence presence = new Presence(Presence.Type.available);
[
] presence.setTo(userName);
[] connection.sendPacket(presence);
[
]
[*] }
[/list]
2. 心情修改

[java] view plaincopyprint?

[list=1]
[]/**
[
] * 修改心情
[] * @param connection
[
] * @param status
[] /
[
] public static void changeStateMessage(XMPPConnection connection,String status)
[
] {
[] Presence presence = new Presence(Presence.Type.available);
[
] presence.setStatus(status);
[] connection.sendPacket(presence);
[
]
[*] }
[/list]
3. 修改用户头像
有点麻烦,主要是读入图片文件,编码,传输之
[java] view plaincopyprint?

[list=1]
[]public static void changeImage(XMPPConnection connection,File f) throws XMPPException, IOException
[
] {
[]
[
] VCard vcard = new VCard();
[] vcard.load(connection);
[
]
[] byte[] bytes;
[
]
[] bytes = getFileBytes(f);
[
] String encodedImage = StringUtils.encodeBase64(bytes);
[] vcard.setAvatar(bytes, encodedImage);
[
] vcard.setEncodedImage(encodedImage);
[] vcard.setField(“PHOTO”, “image/jpg”
[
] + encodedImage + “”, true);
[]
[
]
[] ByteArrayInputStream bais = new ByteArrayInputStream(
[
] vcard.getAvatar());
[] Image image = ImageIO.read(bais);
[
] ImageIcon ic = new ImageIcon(image);
[]
[
]
[]
[
] vcard.save(connection);
[]
[
] }
[]
[
] private static byte getFileBytes(File file) throws IOException {
[] BufferedInputStream bis = null;
[
] try {
[] bis = new BufferedInputStream(new FileInputStream(file));
[
] int bytes = (int) file.length();
[] byte[] buffer = new byte[bytes];
[
] int readBytes = bis.read(buffer);
[] if (readBytes != buffer.length) {
[
] throw new IOException(“Entire file not read”);
[] }
[
] return buffer;
[] } finally {
[
] if (bis != null) {
[] bis.close();
[
] }
[] }
[
]}
[/list]
4. 补充,用户状态的监听

即对方改变头像,状态,心情时,更新自己用户列表,其实这里已经有smack实现的监听器

[java] view plaincopyprint?

[list=1]
[]final Roster roster = Client.getRoster();
[
]
[] roster.addRosterListener(
[
] new RosterListener() {
[]
[
] @Override
[] public void entriesAdded(Collection arg0) {
[
] // TODO Auto-generated method stub
[] System.out.println(“--------EE:”+“entriesAdded”);
[
] }
[]
[
] @Override
[] public void entriesDeleted(Collection arg0) {
[
] // TODO Auto-generated method stub
[] System.out.println(“--------EE:”+“entriesDeleted”);
[
] }
[]
[
] @Override
[] public void entriesUpdated(Collection arg0) {
[
] // TODO Auto-generated method stub
[] System.out.println(“--------EE:”+“entriesUpdated”);
[
] }
[]
[
] @Override
[] public void presenceChanged(Presence arg0) {
[
] // TODO Auto-generated method stub
[] System.out.println(“--------EE:”+“presenceChanged”);
[
] }
[]
[
] });
[*]
[/list]

下一篇主要是文件传输和接收
[5]文件传输及离线消息的获取三天时间,赶在最后一下午实现了文件的传输,本来需要实现离线文件的发送的,一直没想好怎么弄,找openfire的离线文件插件没找到,后来想出一种方法,起服务器时起了一个系统用户,一直在线,当用户发送离线文件,检测到对方不存在,先发给系统用户,存到服务器路径,并在数据库中保存信息,当对方上线时,系统用户查表,拿文件发送

想是这么想的,问题是时间太紧,没有实现,囧。
下一篇写离线消息和离线文件

  1.   文件的发送
    

开一个文件选择框,选中文件后再调用下面的方法

[java] view plaincopyprint?

[list=1]
[]public static void sendFile(XMPPConnection connection,
[
] String user, File file) throws XMPPException, InterruptedException {
[]
[
] System.out.println(“发送文件开始”+file.getName());
[] FileTransferManager transfer = new FileTransferManager(Client.getConnection());
[
] System.out.println(“发送文件给: “+user+Client.getServiceNameWithPre());
[] OutgoingFileTransfer out = transfer.createOutgoingFileTransfer(user+Client.getServiceNameWithPre()+“/Smack”);//
[
]
[] out.sendFile(file, file.getName());
[
]
[] System.out.println(“//////////”);
[
] System.out.println(out.getStatus());
[] System.out.println(out.getProgress());
[
] System.out.println(out.isDone());
[]
[
] System.out.println(”//////////”);
[]
[
] System.out.println(“发送文件结束”);
[*] }
[/list]

  1.   文件接收,必须使用监听
    

[java] view plaincopyprint?

[list=1]
[]FileTransferManager transfer = new FileTransferManager(connection);
[
]transfer.addFileTransferListener(new RecFileTransferListener());
[]
[
]public class RecFileTransferListener implements FileTransferListener {
[]
[
] public String getFileType(String fileFullName)
[] {
[
] if(fileFullName.contains(“.”))
[] {
[
] return “.”+fileFullName.split(“//.”)[1];
[] }else{
[
] return fileFullName;
[] }
[
]
[] }
[
]
[] @Override
[
] public void fileTransferRequest(FileTransferRequest request) {
[] System.out.println(“接收文件开始…”);
[
] final IncomingFileTransfer inTransfer = request.accept();
[] final String fileName = request.getFileName();
[
] long length = request.getFileSize();
[] final String fromUser = request.getRequestor().split(“/”)[0];
[
] System.out.println(“文件大小:”+length + " "+request.getRequestor());
[] System.out.println(“”+request.getMimeType());
[
] try {
[]
[
] JFileChooser chooser = new JFileChooser();
[] chooser.setCurrentDirectory(new File(“.”));
[
]
[] int result = chooser.showOpenDialog(null);
[
]
[] if(result==JFileChooser.APPROVE_OPTION)
[
] {
[] final File file = chooser.getSelectedFile();
[
] System.out.println(file.getAbsolutePath());
[] new Thread(){
[
] public void run()
[] {
[
] try {
[]
[
] System.out.println("接受文件: " + fileName);
[] inTransfer
[
] .recieveFile(new File(file
[] .getAbsolutePath()
[
] + getFileType(fileName)));
[]
[
] Message message = new Message();
[] message.setFrom(fromUser);
[
] message.setProperty(“REC_SIGN”, “SUCCESS”);
[] message.setBody(“[”+fromUser+“]发送文件: “+fileName+”/r/n”+"存储位置: "+file.getAbsolutePath()+ getFileType(fileName));
[
] if (Client.isChatExist(fromUser)) {
[] Client.getChatRoom(fromUser).messageReceiveHandler(
[
] message);
[] } else {
[
] ChatFrameThread cft = new ChatFrameThread(
[] fromUser, message);
[
] cft.start();
[]
[
] }
[] } catch (Exception e2) {
[
] e2.printStackTrace();
[] }
[
] }
[] }.start();
[
] }else{
[]
[
] System.out.println("拒绝接受文件: "+fileName);
[]
[
] request.reject();
[] Message message = new Message();
[
] message.setFrom(fromUser);
[] message.setBody(“拒绝”+fromUser+"发送文件: "+fileName);
[
] message.setProperty(“REC_SIGN”, “REJECT”);
[] if (Client.isChatExist(fromUser)) {
[
] Client.getChatRoom(fromUser)
[] .messageReceiveHandler(message);
[
] } else {
[] ChatFrameThread cft = new ChatFrameThread(
[
] fromUser, message);
[] cft.start();
[
] }
[] }
[
]
[]
[
]
[]
[
]
[] / InputStream in = inTransfer.recieveFile();
[]
[
] String fileName = “r”+inTransfer.getFileName();
[]
[
] OutputStream out = new FileOutputStream(new File(“d:/receive/”+fileName));
[] byte[] b = new byte[512];
[
] while(in.read(b) != -1)
[] {
[
] out.write(b);
[] out.flush();
[
] }
[]
[
] in.close();
[] out.close();/
[] } catch (Exception e) {
[
] e.printStackTrace();
[] }
[
]
[] System.out.println(“接收文件结束…”);
[
]
[] }
[
]
[*]}
[/list]

晕死,在演示的时候竟然发送文件崩盘了。。。。。实在无语
对了,在发送文件的createOutgoing那边有问题,貌似/Smack,哎,对spark发送就不成功

短短三天,查资料差得头晕,中文的信息貌似少之又少,哎,匆匆完成,只能算是个半成品,大家自己完善吧。

呵呵,下一篇最后一篇了,谢谢离线消息和离线文件吧
[6]离线消息和离线文件的实现终篇,三天所学所用,也就这些,如果需要大家要自己去查资料研究研究,功能其实可以很强大的
可惜界面做得不好,一大短处,从大一迄今没整好,主要是个人审美不行,哎

毕业季呀毕业季,明天摆摊卖书,再半月就可能和生活四年的兄弟姐妹说再见,考研考公务员工作的,各奔东西了,我也将南下杭州
感慨,天下无不散之筵席
在此祝所有刚毕业的,学业事业有成吧

不废话了,貌似最近太感性了,理科男,伤不起呀

1.离线消息
openfire本身是支持离线消息的,不需要进行额外处理,可以用spark测试下
使用smack,其实他提供了相应的方法
Class OfflineMessageManager

可以看下描述

The OfflineMessageManager helps manage offline messages even before the user has sent an available presence. When a user asks for his offline messages before sending an available presence then the server will not send a flood with all the offline messages when the user becomes online. The server will not send a flood with all the offline messages to the session that made the offline messages request or to any other session used by the user that becomes online.

英文退化了点,汗,大意就是,必须在发送在线信息之前去获取离线消息

刚开始没看这个,结果在上线之后,去取,结果。。。。离线消息数量总是为零,囧

首先,连接,状态要设为离线

[java] view plaincopy

[list=1]
[]ConnectionConfiguration connConfig = new ConnectionConfiguration(serverDomain);
[
]
[] connConfig.setSendPresence(false); // where connConfig is object of .
[
]
[] connection = new XMPPConnection(connConfig);
[
] connection.connect();
[/list]

然后,登陆
connection.login(userName, pwd);

接着,拿离线消息

[java] view plaincopy

[list=1]
[]OfflineMessageManager offlineManager = new OfflineMessageManager(
[
] Client.getConnection());
[] try {
[
] Iterator<org.jivesoftware.smack.packet.Message> it = offlineManager
[] .getMessages();
[
]
[] System.out.println(offlineManager.supportsFlexibleRetrieval());
[
] System.out.println(“离线消息数量: " + offlineManager.getMessageCount());
[]
[
]
[] Map<String,ArrayList> offlineMsgs = new HashMap<String,ArrayList>();
[
]
[] while (it.hasNext()) {
[
] org.jivesoftware.smack.packet.Message message = it.next();
[] System.out
[
] .println(“收到离线消息, Received from 【” + message.getFrom()
[] + "】 message: " + message.getBody());
[
] String fromUser = message.getFrom().split(”/")[0];
[]
[
] if(offlineMsgs.containsKey(fromUser))
[] {
[
] offlineMsgs.get(fromUser).add(message);
[] }else{
[
] ArrayList temp = new ArrayList();
[] temp.add(message);
[
] offlineMsgs.put(fromUser, temp);
[] }
[
] }
[]
[
] //在这里进行处理离线消息集合…
[] Set keys = offlineMsgs.keySet();
[
] Iterator offIt = keys.iterator();
[] while(offIt.hasNext())
[
] {
[] String key = offIt.next();
[
] ArrayList ms = offlineMsgs.get(key);
[] TelFrame tel = new TelFrame(key);
[
] ChatFrameThread cft = new ChatFrameThread(key, null);
[] cft.setTel(tel);
[
] cft.start();
[] for (int i = 0; i < ms.size(); i++) {
[
] tel.messageReceiveHandler(ms.get(i));
[] }
[
] }
[]
[
]
[] offlineManager.deleteMessages();
[
] } catch (Exception e) {
[] e.printStackTrace();
[
] }
[/list]

记得最后要把离线消息删除,即通知服务器删除离线消息
offlineManager.deleteMessages();
否则,下次上了消息还存在
接着,上线
Presence presence = new Presence(Presence.Type.available);
connection.sendPacket(presence);

2.离线文件

这个我没实现,汗
主要思想:开发openfire插件,拦截离线文件,将文件存到服务器上,同时在数据库里开一张表,存储文件信息
当用户上线时,查表,若是有,根据路径,拿了发送
当然,大家可以谷歌下是否有相应的插件,时间紧迫,我倒是没找着

到这里,大概就这些了,对了,还扩展了个视频音频聊天,不过使用的是JMF,点对点的,本来打算使用jingle的,结果连API文档都没找到,晕死

就这些

LZ,你为什么不开发一款聊天软件呢?

:3_60:这篇帖子好长呀。。

开头就没看懂
[
说是客户端类库,但服务器才需要考虑大量并发用户的问题啊

另外你能不能详细介绍一下xmpp通信的内部stack到底是什么样的,JMF和jingle又是什么,为什么它们能合作?光是个聊天软件的话,我看着API也能写出来啊。

爆料:他正在开发

真的吗? :7_144:

楼主您好,如果我现在需要将本地相册中的图片(类似于文件传输)发送给另一个客户端,需要怎么获取图片名称进行传送呢

一点也看不懂哦,好难啊