您的位置:首页 > 其它

17.4.3 使用MulticastSocket实现多点广播(5)

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

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

程序清单:codes\17\17.4\LanTalk\LanTalk.java

public class LanTalk extends JFrame

{

private DefaultListModel<UserInfo> listModel

= new DefaultListModel<>();

// 定义一个JList对象

private JList<UserInfo> friendsList = new JList<>(listModel);

// 定义一个用于格式化日期的格式器

private DateFormat formatter = DateFormat.getDateTimeInstance();

public LanTalk()

{

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 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 void addUser(UserInfo user)

{

listModel.addElement(user);

}

// 从用户列表中删除用户

public void removeUser(int pos)

{

listModel.removeElementAt(pos);

}

// 获取该聊天窗口的用户数量

public int getUserNum()

{

return listModel.size();

}

// 获取指定位置的用户

public UserInfo getUser(int pos)

{

return 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.getSocketAddress();

// 如果是私聊信息,则该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)

{

LanTalk lanTalk = new LanTalk();

new LoginFrame(lanTalk , "请输入用户名、头像后登录");

}

}

// 定义用于改变JList列表项外观的类

class ImageCellRenderer extends JPanel

implements ListCellRenderer<UserInfo>

{

private ImageIcon icon;

private String name;

// 定义绘制单元格时的背景色

private Color background;

// 定义绘制单元格时的前景色

private Color foreground;

@Override

public Component getListCellRendererComponent(JList list

, UserInfo userInfo , int index

, boolean isSelected , boolean cellHasFocus)

{

// 设置图标

icon = new ImageIcon("ico/" + userInfo.getIcon() + ".gif");

name = userInfo.getName();

// 设置背景色、前景色

background = isSelected ? list.getSelectionBackground()

: list.getBackground();

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路径下的源代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: