一步一步写简易版飞鸽传书(三)

实现公聊

一、前言

我们首先来回顾一下到目前为止我们已经完成的工作。我们已经完成登录窗口、好友列表窗口和聊天窗口以及一个通信类。当我们打开登录窗口,填好用户名选好头像之后,点登录按钮就会弹出一个好友列表窗口,当双击某个好友之后就会打开一个与他聊天的窗口。这里我们发现了一个问题,我们没有做控制。每次双击某个好友都会弹出一个新的聊天窗口。

这当然是不合适的,和谁聊天,我们都应该只打开一个窗口才对。改起来也比较简单,在用户类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


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值