一、创建新项目
 首先新建一个新的项目,并按如下操作

二、实现代码
 界面ChatFrame类
 package 群聊;
  
 import javax.swing.*;
 import java.awt.*;
 import java.awt.event.*;
 import java.net.InetAddress;
  
 public abstract class ChatFrame extends JFrame {
     private JTextArea receiveArea = new JTextArea();//接收文本框,用来显示服务器发送过来的文本
     private JTextArea sendArea = new JTextArea();//发送文本框,用来显示当前用户要发送的文本
  
     private JButton sendBtn = new JButton("SEND");//发送按键
  
     public ChatFrame() {
         this.initFrame();//初始化窗口
         this.initComponent();//初始化组件
         this.initListener();//初始化监听器
         this.receive();//开启监听服务器线程,把接收到的文本显示在receiveArea中
     }
  
     // 初始化监听器
     private void initListener() {
         // 给发送按键添加监听器,当被点击时调用send()方法
         sendBtn.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent evt) {
                 send();
             }
         });
  
         // 给发送文本框添加键盘监听器,当按下Ctrl+ENTER时调用send()方法
         sendArea.addKeyListener(new KeyAdapter() {
             public void keyPressed(KeyEvent e) {
                 if(e.isControlDown()) {
                     if(e.getKeyCode() == KeyEvent.VK_ENTER) {
                         send();
                     }
                 }
             }
         });
     }
  
     // 子类需要重写本方法
     // 在本方法中使用socket实现消息发送
     public abstract void sendText(String text);
  
     // 子类需要重写本方法
     // 在本方法中启动监听服务器线程,调用本类receiveText(String)把接收到的文本显示出来
     public abstract void receive();
  
     // 本方法用来发送文本
     public void send() {
         // 如果发送文本框中没有文本,弹出警告对话框
         if(sendArea.getText().equals("")) {
             javax.swing.JOptionPane.showMessageDialog(this, "空文本不能发送!");
             sendArea.requestFocus();// 把光标归还给发送文本框
             return;
         }
  
         // 调用子类的方法完成文本发送
         sendText(sendArea.getText());
         // 把发送文本框内容清空
         sendArea.setText(null);
     }
  
     // 本方法完成接收服务器消息的后续工作-在文本框中显示服务器消息,子类的receive()方法在接收服务器消息后可以调用本方法
     public void receiveText(String text) {
         receiveArea.append(text);//把接收到的消息添加到文本框中
         // 设置光标位置到最后,如果不设置滚动条不动
         receiveArea.setCaretPosition(receiveArea.getText().length());
     }
  
     // 初始化组件
     private void initComponent() {
         // 使用接收文本框创建滚动窗口(把文本框添加到了滚动窗口中),总是显示纵向滚动条,永不显示横向滚动条
         JScrollPane sp1 = new JScrollPane(receiveArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
         // 设置滚动窗口大小、位置、无边框;并把滚动窗口添加到主窗口中
         sp1.setSize(606, 350);
         sp1.setLocation(14, 20);
         sp1.setBorder(null);
         this.add(sp1);
  
         // 设置接收文本框背景色、不可编辑、自动换行
         receiveArea.setBackground(new Color(238, 238, 238));
         receiveArea.setEditable(false);
         receiveArea.setLineWrap(true);
  
         // 创建发送文本框的滚动窗口,设置自动换行、大小、位置,然后添加到主窗口中
         JScrollPane sp2 = new JScrollPane(sendArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
         sendArea.setLineWrap(true);
         sp2.setSize(606, 145);
         sp2.setLocation(14, 400);
         this.add(sp2);
  
         // 设置发送按键的大小、位置,并添加到主窗口中
         sendBtn.setSize(68, 21);
         sendBtn.setLocation(553, 560);
         this.add(sendBtn);
  
         // 设置主窗口的标题为当前IP地址
         try {
             this.setTitle(InetAddress.getLocalHost().getHostAddress());
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
  
     // 初始化主窗口
     private void initFrame() {
         // 设置主窗口的大小、布局管理器为空、背景色、位置、大小不可改变
         this.setSize(640, 620);
         this.setLayout(null);
         this.setBackground(new Color(246, 246, 247));
         this.setLocation(350, 50);
         this.setResizable(false);
  
         // 设置主窗口的“X”按钮点击后结束程序
         this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     }
  
     // 显示主窗口方法
     public void setVisible(boolean b) {
         super.setVisible(b);//调用父类的显示方法
         sendArea.requestFocus();//让发送文本框得到焦点
     }
 }
组播聊天SocketChat类
 package 群聊;
  
 import java.io.IOException;
 import java.net.DatagramPacket;
 import java.net.InetAddress;
 import java.net.MulticastSocket;
 import java.util.Date;
  
 /**
  * 本类继承了ChatFrame,ChatFrame实现了GUI显示
  * 本类负责使用MulticastSocket完成群聊的发送消息与接收消息
  */
 public class SocketChat extends ChatFrame {
     private MulticastSocket socket;//群组Socket
  
     public SocketChat() throws IOException {
         socket = new MulticastSocket(54321);//创建群组Socket,绑定54321端口
         //加入虚拟IP:235.235.235.235指定的群组中。虚拟IP范围是:224.0.0.1 和 239.255.255.255
         //加入群组后,就可以接收群组的消息,也可以向群组发送消息了
         socket.joinGroup(InetAddress.getByName("235.235.235.235"));
     }
  
     // 发送消息方法
     public void sendText(String text) {
         try {
             // 获取IP地址
             String ip = InetAddress.getLocalHost().getHostAddress();
             // 获取当前时间格式化字符串
             String time = String.format(" <====> %tF %<tT", new Date());
             // 把IP、时间,以及要发送的文本连接在一起
             text = ip + time + "\n" + text + "\n\n";
             // 把文本转换成字节数组
             byte[] buff = text.getBytes();
             // 使用socket向群组发送,socket的send()方法需要两个参数:DatagramPacket、端口号
             // DatagramPacket表示数据包,创建它需要三个参数:数据包的内容、数据包的字节数、要发送的IP地址
             socket.send(new DatagramPacket(buff, buff.length, InetAddress.getByName("235.235.235.235"), 54321));
         } catch(Exception e) {
             e.printStackTrace();
         }
     }
  
     // 本方法用来接收群组发送过来的消息
     public void receive() {
         // 创建监听群组消息的线程,并启动它
         new Thread() {
             public void run() {
                 // 循环监听
                 while(true) {
                     try {
                         // 创建数据包的字节数组,大小为1KB
                         byte[] buff = new byte[1024];
                         // 创建数据包
                         DatagramPacket dp = new DatagramPacket(buff, buff.length);
                         // 接收群组发送过来的消息到数据包中
                         // 本方法会阻塞当前线程,直到接收到消息为止
                         socket.receive(dp);
                         // 把接收到的消息转换成字符串
                         String text = new String(dp.getData(), 0, dp.getLength());
                         // 调用父类的方法完成显示
                         receiveText(text);
                     } catch(Exception e) {}
                 }
             }
         }.start();
     }
  
     public static void main(String[] args) throws IOException {
         SocketChat sc = new SocketChat();
         sc.setVisible(true);
     }
 }
三、运行结果
运行的第一个窗口成员1
第二个窗口为成员2
第三个窗口为成员3
由此可见、新建窗口=添加成员