实现私聊
一、前言
本来,实现私聊和公聊是差不多的,只要再添加一个消息处理方法即可,可是这里为什么要把它单独拿出来成一篇呢?因为,我在这个过程中发现了两个重要的问题。是什么问题呢?我们先不说,先在上一篇的基础上实现私聊功能之后再慢慢揭露。
二、用户地址绑定错误问题
首先,ChatFrame 的按钮点击事件SendAction 的代码改成如下所示:
//定义发送消息按钮点击事件 Action sendAction = new AbstractAction(){ @Override public void actionPerformed(ActionEvent e) { // TODO 自动生成的方法存根 SocketAddress destAddress = user.getAddress(); System.out.println("ChatFrame_destAddress="+destAddress); //公聊“用户”,其address是null if(destAddress == null){ System.out.println("destAddress == null"); LoginFrame.comUtil.broadMsg(chatField.getText()); }else{ LoginFrame.comUtil.sendMsg(chatField.getText(), destAddress); } } };
当我们发现是私聊消息时,就调用 LoginFrame.comUtil.sendMsg 方法。然后在消息处理类 MsgProcessor中增加一个私聊消息处理方法:
/** * 处理私聊消息 * @param broaInPacket * @param friendListFrame */ public void processSingleMsg(DatagramPacket broaInPacket, FriendListFrame friendListFrame){ try { String msg = new String(broaInPacket.getData(), 0, broaInPacket.getLength(),ComUtil.CHARSET); SocketAddress destAddress = broaInPacket.getSocketAddress(); System.out.println("processSingleMsg_destAddress"+destAddress); User toUser = friendListFrame.findUserByAddress(destAddress); ChatFrame toChatFrame = toUser.getChatFrame(); if(toChatFrame == null){ toChatFrame = new ChatFrame(toUser); toUser.setChatFrame(toChatFrame); } if(!toChatFrame.isShowing()){ toChatFrame.setVisible(true); } System.out.println("processSingleMsg_msg="+msg); toChatFrame.addMsg(toUser.getName()+" : "+msg); } catch (UnsupportedEncodingException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } }
最后,在通信类 ComUtil 中的 ReadSingle 线程方法加入
msgProcessor.processSingleMsg(singleInPacket, LoginFrame.friendListFrame);
看起来是没有问题的。我们运行一下看看:
本来,我们打开了一个私聊窗口,输入了 abc ,点击发送信息,可是这条信息却出现在公聊窗口中去了。这是为什么呢?我们在ChatFrame 的 sendAction 中打了日志:
ChatFrame_destAddress=/192.168.48.1:30000
接收到公聊消息=abc
问题就出在端口号 30000 上,用户私聊占用端口应该是 30001。这条私聊消息被发送到多点广播端口上去了,结果被处理公聊消息的线程处理了。那么,这个destAddress是怎么来的?SocketAddress destAddress = user.getAddress();也就是说,用户登录后绑定地址时绑定错了。这个过程在 MsgProcessor 中的processBroadMsg 方法处理用户上线时的逻辑里。
User user = new User(userInfo[0], userInfo[1], broaInPacket.getSocketAddress(),0);
这里是有问题的,broaInPacket.getSocketAddress() 得到的端口号是广播端口号30000 ,按照ComUtil中的约定,应该加1才对。修改之后:
InetSocketAddress srcAddress = (InetSocketAddress) broaInPacket.getSocketAddress(); System.out.println("srcAddress="+srcAddress); SocketAddress theUserAddress = new InetSocketAddress(srcAddress.getHostString(), srcAddress.getPort()+1); User user = new User(userInfo[0], userInfo[1], theUserAddress,0);
这样应该就可以了。
三、多网络接口的问题
到目前为止,我们的程序已经可以完成收发消息的功能了。可是,当我将它打包放到局域网上另一台机器上运行的时候,它却并不能“发现”局域网上的其它用户。这时我想起来我用ipconfig命令在我本机查看时有多个ip地址。《疯狂java讲义》P803页也说过“在某些系统中,可能有多个网络接口。这可能会给多点广播带来问题,这时候程序需要在一个指定的网络接口上监听,通过调用setInterface() 方法可以强制MulticastSocket 使用特定的网络接口。”那么,问题就出在这里了。既然如此,那么我们干脆把所有ip列举出来,让用户自己选择。
新增一个 IPFinder 工具类:
package com.myipmsg.util; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.Vector; /** * 查询所有ip * @author ThinkPad * */ public class IPFinder { public static Vector<String> find(){ Vector<String> ipList = new Vector<>(); Enumeration<NetworkInterface> netInterfaces = null; try { netInterfaces = NetworkInterface.getNetworkInterfaces(); while (netInterfaces.hasMoreElements()) { NetworkInterface ni = netInterfaces.nextElement(); Enumeration<InetAddress> ips = ni.getInetAddresses(); while (ips.hasMoreElements()) { String ip = ips.nextElement().getHostAddress(); if(!ip.contains(":") && !ip.equals("127.0.0.1")){ ipList.add(ip); } } } } catch (Exception e) { e.printStackTrace(); } System.out.println(ipList); return ipList; } public static void main(String[] args) { // TODO 自动生成的方法存根 find(); } }
工程中其它地方的修改这里就不一一赘述了,有兴趣的可以去查看附件。修改之后的登录窗口:
好友列表中把用户的ip也一并显示出来了:
注:本来还有第五篇,写传输文件的,因为之前在自己局域网中测试发送文字信息时老是不成功,备受打击,所以索性没写了。那就不写了吧。代码在附件中都有。
所有代码可在此处下载:http://download.csdn.net/detail/zhutulang/9207885