• Java教程:如何利用UDP实现群聊聊天室?


    ​ 大家好,今天为大家带来了一个非常有意思的小程序——UDP实现的群聊聊天室。这个程序使用的UDP协议,并使用DatagramSocket的子类MulticastSocket实现组播,可以部署在一个局域网内的多台电脑上,并可以实现文字群聊。

    ​ 本文将会按照以下几个小节讲解:

    ​ 1). 组播的概念:这个小节我们将讲解什么是:单播、广播、组播。

    ​ 2). MulticastSocket类的使用:这个小节我们将讲解MulticastSocket类的基本使用,并实现控制台的信息收发。

    ​ 3). 基于Swing和MulticastSocket实现的UDP群聊聊天室:**这个小节我们将制作一个界面,并结合MulticastSocket类实现一个完整的UDP群聊聊天室。

    ​ 4). 结束语

    一、组播的概念

    ​ 网络数据传播按照接收者的数量,可分为以下3种方式:

    1.1 单播:

    ​ 单播是指实现“点对点”的通信,发送者发送数据要发送给网络上的唯一的一台电脑,指定一个接收者。像TCP协议和UDP协议都能实现点对点通信。

    1.2 广播:

    ​ 发送者发送的数据可以被某个接收范围内所有的接收者接收。它类似于广播电台,向某个范围内的所有用户发送广播信号,接收人打开广播就可以听到,关闭广播设备就停止收听。由于广播会大大增加网络数据流量,所以通常情况下一些网络路由器会禁止广播数据,尤其是一些占用网络资源比较大的视频数据等。

    1.3 组播:

    ​ 组播是指发送的数据可以被指定的一组用户接收。组播的范围没有广播那么广,任何的一台电脑都可以随时加入某一个组接收组播数据。若要使用组播,则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有主机都能收到该数据报。IP协议为组播提供了这批特殊的IP地址,这些IP地址的范围是224.0.0.0至239.255.255.255。在Java类库中,DatagramSocket有一个子类:MulticastSocket,它具有组播的功能,它可以与DatagramPackage结合使用,用于发送和接收组播包。

    二. MulticastSocket类的使用

    ​ Java类库中MulticastSocket类可以实现组播功能,它是DatagramSocket的子类:

    在这里插入图片描述

    2.1 构造方法说明

    ​ 通过API文档我们可以看到它有三个构造方法:

    1. MulticastSocket() 创建一个多播套接字。(使用随机端口,如果只发送,可以使用这个构造方法) 
    2. MulticastSocket(int port) 创建一个多播套接字并将其绑定到一个特定的端口。(如果需要发送和接收,需要使用这个构造方法)
    3. MulticastSocket(SocketAddress bindaddr) 创建一个多播套接字绑定到指定的套接字地址。 
    
    • 1
    • 2
    • 3

    2.2 成员方法说明

    ​ 以下是几个比较重要的成员方法:

    1.public void joinGroup(InetAddress mcastaddr):将该MulticastSocket加入指定的多点广播地址。
    2.public void leaveGroup(InetAddress mcastaddr让该MulticastSocket离开指定的多点广播地址。
    3.public void setInterface(InetAddress inf):如果当前系统有多个网络接口,可以使用次方法指定一个网络接口。
    4.public InetAddress getInterface():获取当前的网络接口。
    5.public void setTimeToLive(int ttl):该参数设置数据报最多可以跨过多少个网络,当ttl为0时,指定数据报应停留在本地主机;当ttl的值为1时,指定数据报发送到本地局域网;当ttl的值为32时,意味着只能发送到本站点的网络上;当ttl为64时,意味着数据报应保留在本地区;当ttl的值为128时,意味着数据报应保留在本大洲;当ttl为255时,意味着数据报可发送到所有地方;默认情况下,该ttl的值为1
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3 一个简单的示例

    ​ 接下来我们写一个小例子来看一下MulticastSocket的使用方式。这个程序将包含两个线程:1. 接收线程,主要用于接收信息;2. 主线程,主要用于发送信息。将这个程序部署到局域网上的几台电脑上,全部启动,就可以实现多台电脑的组播了,而且每台主机都可以发出信息,其它主机则会收到这条信息。

    package com.heima.se.chat;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.InetAddress;
    import java.net.MulticastSocket;
    import java.util.Date;
    import java.util.Scanner;
    
    public class MulticastSocketDemo {
        public static void main(String[] args) throws IOException {
            //创建MuticastSocket对象,并监听端口55555
            MulticastSocket socket = new MulticastSocket(55555);
            //加入组:235.235.235.235
            socket.joinGroup(InetAddress.getByName("235.235.235.235"));
    
            //启动线程-此线程用于接收数据报
            new Thread(()->{
                byte[] bytes = new byte[1024];
                DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
                while (true) {
                    try {
                        socket.receive(packet);
                        System.out.println(new String(packet.getData(), 0, packet.getLength()));
    
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
            //获取本机IP
            String localIp = InetAddress.getLocalHost().getHostAddress();
    
            //创建一个Scanner对象,用于接收控制台数据
            Scanner sc = new Scanner(System.in);
            while (true) {
                System.out.println("【请输入信息】");
                String msg = sc.next();
                // 获取当前时间格式化字符串,把IP、时间,以及要发送的文本连接在一起
                String time = String.format(" <====> %tF %, new Date());
                msg = localIp + time + "\n" + msg + "\n\n";
                //发送数据报
                socket.send(new DatagramPacket(msg.getBytes(),
                                                msg.getBytes().length,
                                                InetAddress.getByName("235.235.235.235"),
                                                55555));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    ​ 通过上面的程序,我们发现,MulticastSocket类的使用和DatagramSocket类基本相同,只是多了一步加入组:joinGroup(),所有加入这个组的主机都将会收到信息。

    三. 基于Swing和MulticastSocket实现的UDP群聊聊天室

    ​ 接下来我们使用Swing为这个程序制作一个界面,让用户操作起来更加方便。

    ​ 这个程序我们制作了两个类:

    ​ 1). ChatFrame:这个类继承自JFrame,实现了界面的显示、布局等相关功能。

    ​ 2). SocketChat:这个类继承自ChatFrame,加入了MulticastSocket的连接、信息发送和接收。

    3.1 界面ChatFrame类

    package com.heima.se.chat;
    
    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();//让发送文本框得到焦点
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127

    ​ 这个类中定义了很多抽象方法,这些抽象方法由子类实现。

    3.2 组播聊天SocketChat类

    package com.heima.se.chat;
    
    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 %, 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
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    ​ 这个类使用MulticastSocket,使用端口:54321,组播地址:235.235.235.235。当用户在界面按下send按钮时,会触发sendText()方法发送数据;receive()方法用于使用线程接收数据,它是在父类的构造方法中被触发启动,启动后,使用无限循环进行信息的接收。

    四.结束语

    ​ 这篇文章我们使用MulticastSocket类实现了组播功能,并使用Swing和MulticastSocket制作了一个基于UDP的群聊聊天室,希望大家能够通过本篇文章了解MulticastSocket类的使用,有兴趣的朋友可以基于这个程序,为它添加更多的功能,例如:文件的发送、接收;表情图片的发送、接收等等。后面我还会为大家带来更多更实用的程序,期待大家来围观哦!谢谢大家!!

  • 相关阅读:
    中国多媒体与网络教学学报杂志中国多媒体与网络教学学报杂志社中国多媒体与网络教学学报编辑部2022年第6期目录
    大数据-玩转数据-Flink页面广告点击量统计
    MasterAlign相机参数设置-曝光时间调节
    SAP Commerce Cloud 的 Security 策略概述
    【进程和线程】
    【数据挖掘】PCA 主成分分析算法过程及原理讲解
    数据服务器之raid1使用
    Java实现单链表
    Word文档里面如何给内容进行注释添加
    LC501. 二叉搜索树中的众数
  • 原文地址:https://blog.csdn.net/cz_00001/article/details/126890198