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


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

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

    程序清单:codes1717.4LanTalkLanTalk.java

    1. public class LanTalk extends JFrame
    2. {
    3. private DefaultListModel<UserInfo> listModel
    4. = new DefaultListModel<>();
    5. // 定义一个JList对象
    6. private JList<UserInfo> friendsList = new JList<>(listModel);
    7. // 定义一个用于格式化日期的格式器
    8. private DateFormat formatter = DateFormat.getDateTimeInstance();
    9. public LanTalk()
    10. {
    11. super("局域网聊天");
    12. // 设置该JList使用ImageCellRenderer作为单元格绘制器
    13. friendsList.setCellRenderer(new ImageCellRenderer());
    14. listModel.addElement(new UserInfo("all" , "所有人"
    15. , null , -2000));
    16. friendsList.addMouseListener(new ChangeMusicListener());
    17. add(new JScrollPane(friendsList));
    18. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    19. setBounds(2, 2, 160 , 600);
    20. }
    21. // 根据地址来查询用户
    22. public UserInfo getUserBySocketAddress(SocketAddress address)
    23. {
    24. for (int i = 1 ; i < getUserNum() ; i++)
    25. {
    26. UserInfo user = getUser(i);
    27. if (user.getAddress() != null
    28. && user.getAddress().equals(address))
    29. {
    30. return user;
    31. }
    32. }
    33. return null;
    34. }
    35. // ------下面四个方法是对ListModel的包装------
    36. // 向用户列表中添加用户
    37. public void addUser(UserInfo user)
    38. {
    39. listModel.addElement(user);
    40. }
    41. // 从用户列表中删除用户
    42. public void removeUser(int pos)
    43. {
    44. listModel.removeElementAt(pos);
    45. }
    46. // 获取该聊天窗口的用户数量
    47. public int getUserNum()
    48. {
    49. return listModel.size();
    50. }
    51. // 获取指定位置的用户
    52. public UserInfo getUser(int pos)
    53. {
    54. return listModel.elementAt(pos);
    55. }
    56. // 实现JList上的鼠标双击事件监听器
    57. class ChangeMusicListener extends MouseAdapter
    58. {
    59. public void mouseClicked(MouseEvent e)
    60. {
    61. // 如果鼠标的击键次数大于2
    62. if (e.getClickCount() >= 2)
    63. {
    64. // 取出鼠标双击时选中的列表项
    65. UserInfo user = (UserInfo)friendsList.getSelectedValue();
    66. // 如果该列表项对应用户的交谈窗口为null
    67. if (user.getChatFrame() == null)
    68. {
    69. // 为该用户创建一个交谈窗口,并让该用户引用该窗口
    70. user.setChatFrame(new ChatFrame(null , user));
    71. }
    72. // 如果该用户的窗口没有显示,则让该用户的窗口显示出来
    73. if (!user.getChatFrame().isShowing())
    74. {
    75. user.getChatFrame().setVisible(true);
    76. }
    77. }
    78. }
    79. }
    80. /**
    81. * 处理网络数据报,该方法将根据聊天信息得到聊天者
    82. * 并将信息显示在聊天对话框中
    83. * @param packet 需要处理的数据报
    84. * @param single 该信息是否为私聊信息
    85. */
    86. public void processMsg(DatagramPacket packet , boolean single)
    87. {
    88. // 获取发送该数据报的SocketAddress
    89. InetSocketAddress srcAddress = (InetSocketAddress)
    90. packet.getSocketAddress();
    91. // 如果是私聊信息,则该Packet获取的是DatagramSocket的地址
    92. // 将端口号减1才是对应的MulticastSocket的地址
    93. if (single)
    94. {
    95. srcAddress = new InetSocketAddress(srcAddress.getHostName()
    96. , srcAddress.getPort() - 1);
    97. }
    98. UserInfo srcUser = getUserBySocketAddress(srcAddress);
    99. if (srcUser != null)
    100. {
    101. // 确定消息将要显示到哪个用户对应的窗口中
    102. UserInfo alertUser = single ? srcUser : getUser(0);
    103. // 如果该用户对应的窗口为空,则显示该窗口
    104. if (alertUser.getChatFrame() == null)
    105. {
    106. alertUser.setChatFrame(new ChatFrame(null , alertUser));
    107. }
    108. // 定义添加的提示信息
    109. String tipMsg = single ? "对您说:" : "对大家说:";
    110. // 显示提示信息
    111. alertUser.getChatFrame().addString(srcUser.getName()
    112. + tipMsg + "......................("
    113. + formatter.format(new Date()) + ") "
    114. + new String(packet.getData() , 0 , packet.getLength())
    115. + " ");
    116. if (!alertUser.getChatFrame().isShowing())
    117. {
    118. alertUser.getChatFrame().setVisible(true);
    119. }
    120. }
    121. }
    122. // 主方法,程序的入口
    123. public static void main(String[] args)
    124. {
    125. LanTalk lanTalk = new LanTalk();
    126. new LoginFrame(lanTalk , "请输入用户名、头像后登录");
    127. }
    128. }
    129. // 定义用于改变JList列表项外观的类
    130. class ImageCellRenderer extends JPanel
    131. implements ListCellRenderer<UserInfo>
    132. {
    133. private ImageIcon icon;
    134. private String name;
    135. // 定义绘制单元格时的背景色
    136. private Color background;
    137. // 定义绘制单元格时的前景色
    138. private Color foreground;
    139. @Override
    140. public Component getListCellRendererComponent(JList list
    141. , UserInfo userInfo , int index
    142. , boolean isSelected , boolean cellHasFocus)
    143. {
    144. // 设置图标
    145. icon = new ImageIcon("ico/" + userInfo.getIcon() + ".gif");
    146. name = userInfo.getName();
    147. // 设置背景色、前景色
    148. background = isSelected ? list.getSelectionBackground()
    149. : list.getBackground();
    150. foreground = isSelected ? list.getSelectionForeground()
    151. : list.getForeground();
    152. // 返回该JPanel对象作为单元格绘制器
    153. return this;
    154. }
    155. // 重写paintComponent方法,改变JPanel的外观
    156. public void paintComponent(Graphics g)
    157. {
    158. int imageWidth = icon.getImage().getWidth(null);
    159. int imageHeight = icon.getImage().getHeight(null);
    160. g.setColor(background);
    161. g.fillRect(0, 0, getWidth(), getHeight());
    162. g.setColor(foreground);
    163. // 绘制好友图标
    164. g.drawImage(icon.getImage() , getWidth() / 2 - imageWidth / 2
    165. , 10 , null);
    166. g.setFont(new Font("SansSerif" , Font.BOLD , 18));
    167. // 绘制好友用户名
    168. g.drawString(name, getWidth() / 2 - name.length() * 10
    169. , imageHeight + 30 );
    170. }
    171. // 通过该方法来设置该ImageCellRenderer的最佳大小
    172. public Dimension getPreferredSize()
    173. {
    174. return new Dimension(60, 80);
    175. }
    176. }

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

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

  • 相关阅读:
    8088汇编跳转和PSW状态字寄存器
    Delphi的函数指针
    服务器系统及软件常见漏洞
    TGraphiControl响应WM_MOUSEMOVE的过程(以TPaintBox为例)good
    两个奇怪的取地址符号
    把x指针指向的4个字节次序颠倒过来
    DELPHI中的消息处理机制(三种消息处理方法的比较,如何截断消息)
    探索C++的底层机制
    setprecision、fixed、showpoint的用法总结(经典!!超经典!!)
    段寄存器和8种地址寻址方式
  • 原文地址:https://www.cnblogs.com/senior-engineer/p/4967279.html
Copyright © 2020-2023  润新知