本文要解决的问题:
整理之前在大连东软实习做的一个局域网内部实时通信系统,进行一个系统的回顾。
实现私聊
一、前言
本来,实现私聊和公聊是差不多的,只要再添加一个消息处理方法即可,可是这里为什么要把它单独拿出来成一篇呢?因为,我在这个过程中发现了两个重要的问题。是什么问题呢?我们先不说,先在上一篇的基础上实现私聊功能之后再慢慢揭露。
二、用户地址绑定错误问题
首先,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也一并显示出来了: