使用MulticastSocket实现多点广播(4)

该类主要实现底层的网络通信功能,在该类中提供了一个broadCast方法,该方法使用MulticastSocket将指定字符串广播到所有客户端,还提供了sendSingle方法,该方法使用DatagramSocket将指定字符串发送到指定SocketAddress,如程序中前两行粗体字代码所示。除此之外,该类里还提供了2个内部线程类:ReadSingle和ReadBroad,这两个线程类采用循环不断读取DatagramSocket和MulticastSocket中的数据,如果读到的信息是广播来的在线信息,则保持该用户在线;如果读到的是用户的聊天信息,则直接将该信息显示出来。

在该类中用到了本程序的一个主类:LanChat,该类使用DefaultListModel来维护用户列表,该类里的每个列表项就是一个UserInfo。该类还提供了一个ImageCellRenderer,该类用于将列表项绘制出用户图标和用户名字。

程序清单:codes/17/17-4/LanChat/LanChat.java

public class LanChat extends JFrame
{
private DefaultListModel listModel = new DefaultListModel();
//定义一个JList对象
private JList friendsList = new JList(listModel);
//定义一个用于格式化日期的格式器
private DateFormat formatter = DateFormat.getDateTimeInstance();
public LanChat()
{
super("局域网聊天");
//设置该JList使用ImageCellRenderer作为单元格绘制器
friendsList.setCellRenderer(new ImageCellRenderer()); 
listModel.addElement(new UserInfo("all" , "所有人" , null , -2000));
friendsList.addMouseListener(new ChangeMusicListener());
add(new JScrollPane(friendsList));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(2, 2, 160 , 600);
}
//向用户列表中添加用户
public void addUser(UserInfo user)
{
listModel.addElement(user);
}
//从用户列表中删除用户
public void removeUser(int pos)
{
listModel.removeElementAt(pos);
}
//根据地址来查询用户
public UserInfo getUserBySocketAddress(SocketAddress address)
{
for (int i = 1 ; i < getUserNum() ; i++)
{
UserInfo user = getUser(i);
if (user.getAddress() != null && 
user.getAddress().equals(address))
{
return user;
}
}
return null;
}
//—————————下面两个方法是对ListModel的包装—————————
//获取该聊天窗口的用户数量
public int getUserNum()
{
return listModel.size();
}
//获取指定位置的用户
public UserInfo getUser(int pos)
{
return (UserInfo)listModel.elementAt(pos);
}
//实现JList上的鼠标双击监听器
class ChangeMusicListener extends MouseAdapter
{
public void mouseClicked(MouseEvent e)
{
//如果鼠标的击键次数大于2
if (e.getClickCount() >= 2)
{
//取出鼠标双击时选中的列表项
UserInfo user = (UserInfo)friendsList.getSelectedValue();
//如果该列表项对应用户的交谈窗口为null
if (user.getChatFrame() == null)
{
//为该用户创建一个交谈窗口,并让该用户引用该窗口
user.setChatFrame(new ChatFrame(null , user));
}
//如果该用户的窗口没有显示,则让该用户的窗口显示出来
if (!user.getChatFrame().isShowing())
{
user.getChatFrame().setVisible(true);
}
}
}
}
/**
* 处理网络数据报,该方法将根据聊天信息得到聊天者,
* 并将信息显示在聊天对话框中。
* @param packet 需要处理的数据报
* @param single 该信息是否为私聊信息
*/
public void processMsg(DatagramPacket packet , boolean single)
{
//获取该发送该数据报的SocketAddress
InetSocketAddress srcAddress = (InetSocketAddress)packet.getSocket
Address();
//如果是私聊信息,则该Packet获取的是DatagramSocket的地址,将端口减1才是
//对应的MulticastSocket的地址
if (single)
{
srcAddress = new InetSocketAddress(srcAddress.getHostName(),
srcAddress.getPort() - 1);
}
UserInfo srcUser = getUserBySocketAddress(srcAddress);
if (srcUser != null)
{
//确定消息将要显示到哪个用户对应窗口上。
UserInfo alertUser = single ? srcUser : getUser(0);
//如果该用户对应的窗口为空,显示该窗口
if (alertUser.getChatFrame() == null)
{
alertUser.setChatFrame(new ChatFrame(null , alertUser));
}
//定义添加的提示信息
String tipMsg = single ? "对您说:" : "对大家说:";
//显示提示信息
alertUser.getChatFrame().addString(srcUser.getName() + tipMsg
+ "......................(" + formatter.format(new Date()) + ")\n"
+ new String(packet.getData() , 0 , packet.getLength()) + "\n");
if (!alertUser.getChatFrame().isShowing())
{
alertUser.getChatFrame().setVisible(true);
}
}
}
//主方法,程序的入口
public static void main(String[] args) 
{
LanChat lc = new LanChat();
new LoginFrame(lc , "请输入用户名、头像后登录");
}
}
//定义用于改变JList列表项外观的类
class ImageCellRenderer extends JPanel implements ListCellRenderer
{
private ImageIcon icon;
private String name;
//定义绘制单元格时的背景色
private Color background;
//定义绘制单元格时的前景色
private Color foreground;
public Component getListCellRendererComponent(JList list, Object value, int 
index,   boolean isSelected, boolean cellHasFocus)
{
UserInfo userInfo = (UserInfo)value;
icon = new ImageIcon("ico/" + userInfo.getIcon() + ".gif");
name = userInfo.getName();
background = isSelected ? list.getSelectionBackground() : list.getBack
ground();
foreground = isSelected ? list.getSelectionForeground() : list.
getForeground();
//返回该JPanel对象作为单元格绘制器
return this;
}
//重写paintComponent方法,改变JPanel的外观
public void paintComponent(Graphics g)
{
int imageWidth = icon.getImage().getWidth(null);
int imageHeight = icon.getImage().getHeight(null);
g.setColor(background);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(foreground);
//绘制好友图标
g.drawImage(icon.getImage() , getWidth() / 2 - imageWidth / 2 , 10 , null);
g.setFont(new Font("SansSerif" , Font.BOLD , 18));
//绘制好友用户名
g.drawString(name, getWidth() / 2 - name.length() * 10 , imageHeight + 30 );
}
//通过该方法来设置该ImageCellRenderer的最佳大小
public Dimension getPreferredSize()
{  
return new Dimension(60, 80);
}
}

上面类中提供的addUser和removeUser方法用于暴露给通信类ComUtil使用,用于向用户列表中添加、删除用户。除此之外,该类还提供了一个processMsg方法,该方法用于处理网络中读取的数据报,将数据报中的内容取出,并显示在特定的窗口中。

上面讲解的只是本程序的关键类,本程序还涉及YeekuProtocol、ChatFrame、LoginFrame等类,由于篇幅关系,此处不再给出这些类的源代码,读者可以参考codes/17/17-4/LanTalk路径下的源代码。

你可能感兴趣的:(使用MulticastSocket实现多点广播(4))