实现公聊
一、前言
我们首先来回顾一下到目前为止我们已经完成的工作。我们已经完成登录窗口、好友列表窗口和聊天窗口以及一个通信类。当我们打开登录窗口,填好用户名选好头像之后,点登录按钮就会弹出一个好友列表窗口,当双击某个好友之后就会打开一个与他聊天的窗口。这里我们发现了一个问题,我们没有做控制。每次双击某个好友都会弹出一个新的聊天窗口。
这当然是不合适的,和谁聊天,我们都应该只打开一个窗口才对。改起来也比较简单,在用户类User中加一个ChatFrame 的实例,表明这个用户对应的聊天窗口。
//该用户对应的聊天窗口 private ChatFrame chatFrame; public ChatFrame getChatFrame() { return chatFrame; } public void setChatFrame(ChatFrame chatFrame) { this.chatFrame = chatFrame; } FriendListFrame 中ShowChatFrameListener 修改如下: //实现JList上的鼠标双击事件的监听器 class ShowChatFrameListener extends MouseAdapter{ public void mouseClicked(MouseEvent e) { //双击 if(e.getClickCount() >= 2){ User user = friendList.getSelectedValue(); if(user.getChatFrame() == null){ user.setChatFrame(new ChatFrame(user)); } //如果该用户的聊天窗口没有显示,则让该用户的聊天窗口显示出来 if (!user.getChatFrame().isShowing()) { user.getChatFrame().setVisible(true); } } } }
这样就OK了。
二、处理用户上线、下线和公聊
接下来还有一个问题需要解决,那就是及时更新好友列表的问题。当有新用户上线时,我们要把他加入到好友列表中,当他下线时我们要将他从好友列表中剔除。实现的思路就是用户登录后就定时广播一条特殊的消息——在线消息,而用户关闭所有窗口退出后,显然就不会再广播这条消息了。关键的部分代码在这里再简单解释下,大部分都是直接拷贝自《疯狂java讲义》。
首先,用户类User中新增了两个属性,SocketAddress address 和 int lost。address 用来唯一地标识一个用户,lost标识该用户失去联系的次数,当失联次数大于2时就让他下线。
//该用户所在的IP和端口 private SocketAddress address; //该用户失去联系的次数 private int lost; //使用address作为该用户的标识,所以根据address作为 //重写hashCode()和equals方法的标准 public int hashCode(){ return address.hashCode(); } public boolean equals(Object obj){ if(obj != null && obj instanceof User){ User target = (User) obj; if(address != null){ return address.equals(target.getAddress()); } } return false; }
在登录按钮的事件中,我们构造一条特殊的广播消息——用户上线消息,然后定义一个定时器。登录按钮的事件代码如下:
@Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub //显示好友列表窗口 friendListFrame = new FriendListFrame(); loginFrame.setVisible(false); //实例化通信工具类 comUtil = new ComUtil(); //构造用户上线消息 String userOnlineMsg = MyProtocol.PRESENCE+userNameField.getText()+MyProtocol.SPLITTER+iconList.getSelectedIndex()+".jpg"+MyProtocol.PRESENCE; comUtil.broadMsg(userOnlineMsg); // 启动定时器每3秒广播一次在线消息 Timer timer= new Timer(1000 * 3 , new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // TODO 自动生成的方法存根 comUtil.broadMsg(userOnlineMsg); } }); timer.start(); }
另外,我们定义了一个消息处理类MsgProcessor ,用它来处理消息。在其中,暂时提供一个处理公聊消息的方法,根据消息是普通公聊消息还是用户上线消息,分别做了相应的处理。代码如下:
package com.myipmsg.util; import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.net.SocketAddress; import java.util.ArrayList; import java.util.List; import com.myipmsg.bean.User; import com.myipmsg.comutil.ComUtil; import com.myipmsg.frame.ChatFrame; import com.myipmsg.frame.FriendListFrame; /** * 消息处理工具类 * @author ThinkPad * */ public class MsgProcessor { /** * 处理公聊消息 * @param msg */ public void processBroadMsg(DatagramPacket broaInPacket, FriendListFrame friendListFrame){ try { String msg = new String(broaInPacket.getData(), 0, broaInPacket.getLength(),ComUtil.CHARSET); if(msg.startsWith(MyProtocol.PRESENCE) && msg.endsWith(MyProtocol.PRESENCE)) { //说明是用户上线消息 String userMsg = msg.substring(2, msg.length() - 2); String[] userInfo = userMsg.split(MyProtocol.SPLITTER); User user = new User(userInfo[0], userInfo[1], broaInPacket.getSocketAddress(),0); //表明该用户是否是刚刚上线,是否需要添加到好友列表中 boolean needAdd = true; //保存需要被删除的用户下标 List<Integer> toBeRemoveList = new ArrayList<>(); for(int i=1;i<friendListFrame.getUserNum();i++){ //从1开始,是因为好友列表中第一个是“所有用户”,让它一直在线 User theUser = friendListFrame.getUser(i); //首先不管三七二十一,将它的失去联系次数加1 theUser.setLost(theUser.getLost()+1); if(theUser.equals(user)){ needAdd = false; //如果检测到了该用户,那么将他的失联次数置为0 theUser.setLost(0); } //如果用户失联次数大于2,那么应该要将他从好友列表中删除 if(theUser.getLost() > 2){ toBeRemoveList.add(i); } } // 删除toBeRemoveList中的所有索引对应的用户 for (int i = 0; i < toBeRemoveList.size() ; i++) { friendListFrame.removeUser(toBeRemoveList.get(i)); } //加入新上线的用户 if(needAdd){ friendListFrame.addUser(user); } }else{ //说明是普通的公聊消息,那么打开公聊窗口,显示消息 //第1个用户是所有人 ChatFrame publicChatFrame = null; if(friendListFrame.getUser(0).getChatFrame() == null){ publicChatFrame = new ChatFrame(friendListFrame.getUser(0)); friendListFrame.getUser(0).setChatFrame(publicChatFrame); }else{ publicChatFrame = friendListFrame.getUser(0).getChatFrame(); } if(!publicChatFrame.isShowing()){ publicChatFrame.setVisible(true); } SocketAddress address = broaInPacket.getSocketAddress(); publicChatFrame.addMsg(friendListFrame.findUserByAddress(address).getName()+" : "+msg); } } catch (UnsupportedEncodingException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } public static void main(String[] args) { // TODO 自动生成的方法存根 } }
注意:本篇为了实现公聊功能,很多类都有修改。这里不一一赘述,详细的代码请看附件。
所有代码可在此处下载: http://download.csdn.net/detail/zhutulang/9207885