实现公聊
一、前言
我们首先来回顾一下到目前为止我们已经完成的工作。我们已经完成登录窗口、好友列表窗口和聊天窗口以及一个通信类。当我们打开登录窗口,填好用户名选好头像之后,点登录按钮就会弹出一个好友列表窗口,当双击某个好友之后就会打开一个与他聊天的窗口。这里我们发现了一个问题,我们没有做控制。每次双击某个好友都会弹出一个新的聊天窗口。
这当然是不合适的,和谁聊天,我们都应该只打开一个窗口才对。改起来也比较简单,在用户类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