首頁技術(shù)文章正文

Java培訓(xùn):UDP實(shí)現(xiàn)群聊聊天室

更新時(shí)間:2022-09-16 來源:黑馬程序員 瀏覽量:

  大家好,今天為大家?guī)砹艘粋€(gè)非常有意思的小程序——UDP實(shí)現(xiàn)的群聊聊天室。這個(gè)程序使用的UDP協(xié)議,并使用DatagramSocket的子類MulticastSocket實(shí)現(xiàn)組播,可以部署在一個(gè)局域網(wǎng)內(nèi)的多臺(tái)電腦上,并可以實(shí)現(xiàn)文字群聊。

   本文將會(huì)按照以下幾個(gè)小節(jié)講解:

   1). 組播的概念:**這個(gè)小節(jié)我們將講解什么是:單播、廣播、組播。

   2). MulticastSocket類的使用:**這個(gè)小節(jié)我們將講解MulticastSocket類的基本使用,并實(shí)現(xiàn)控制臺(tái)的信息收發(fā)。

   3). 基于Swing和MulticastSocket實(shí)現(xiàn)的UDP群聊聊天室:**這個(gè)小節(jié)我們將制作一個(gè)界面,并結(jié)合MulticastSocket類實(shí)現(xiàn)一個(gè)完整的UDP群聊聊天室。

   4). 結(jié)束語:

  一、組播的概念

   網(wǎng)絡(luò)數(shù)據(jù)傳播按照接收者的數(shù)量,可分為以下3種方式:

  1.1 單播:

   單播是指實(shí)現(xiàn)“點(diǎn)對點(diǎn)”的通信,發(fā)送者發(fā)送數(shù)據(jù)要發(fā)送給網(wǎng)絡(luò)上的唯一的一臺(tái)電腦,指定一個(gè)接收者。像TCP協(xié)議和UDP協(xié)議都能實(shí)現(xiàn)點(diǎn)對點(diǎn)通信。

  1.2 廣播:

   發(fā)送者發(fā)送的數(shù)據(jù)可以被某個(gè)接收范圍內(nèi)所有的接收者接收。它類似于廣播電臺(tái),向某個(gè)范圍內(nèi)的所有用戶發(fā)送廣播信號,接收人打開廣播就可以聽到,關(guān)閉廣播設(shè)備就停止收聽。由于廣播會(huì)大大增加網(wǎng)絡(luò)數(shù)據(jù)流量,所以通常情況下一些網(wǎng)絡(luò)路由器會(huì)禁止廣播數(shù)據(jù),尤其是一些占用網(wǎng)絡(luò)資源比較大的視頻數(shù)據(jù)等。

  1.3 組播:

   組播是指發(fā)送的數(shù)據(jù)可以被指定的一組用戶接收。組播的范圍沒有廣播那么廣,任何的一臺(tái)電腦都可以隨時(shí)加入某一個(gè)組接收組播數(shù)據(jù)。若要使用組播,則需要讓一個(gè)數(shù)據(jù)報(bào)標(biāo)有一組目標(biāo)主機(jī)地址,當(dāng)數(shù)據(jù)報(bào)發(fā)出后,整個(gè)組的所有主機(jī)都能收到該數(shù)據(jù)報(bào)。IP協(xié)議為組播提供了這批特殊的IP地址,這些IP地址的范圍是224.0.0.0至239.255.255.255。在Java類庫中,DatagramSocket有一個(gè)子類:MulticastSocket,它具有組播的功能,它可以與DatagramPackage結(jié)合使用,用于發(fā)送和接收組播包。

  二. MulticastSocket類的使用

   Java類庫中MulticastSocket類可以實(shí)現(xiàn)組播功能,它是DatagramSocket的子類:

1663291462281_1.jpg

  2.1 構(gòu)造方法說明

   通過API文檔我們可以看到它有三個(gè)構(gòu)造方法:

1. MulticastSocket() 創(chuàng)建一個(gè)多播套接字。(使用隨機(jī)端口,如果只發(fā)送,可以使用這個(gè)構(gòu)造方法)
2. MulticastSocket(int port) 創(chuàng)建一個(gè)多播套接字并將其綁定到一個(gè)特定的端口。(如果需要發(fā)送和接收,需要使用這個(gè)構(gòu)造方法)
3. MulticastSocket(SocketAddress bindaddr) 創(chuàng)建一個(gè)多播套接字綁定到指定的套接字地址。

  2.2 成員方法說明

   以下是幾個(gè)比較重要的成員方法:

1.public void joinGroup(InetAddress mcastaddr):將該MulticastSocket加入指定的多點(diǎn)廣播地址。
2.public void leaveGroup(InetAddress mcastaddr讓該MulticastSocket離開指定的多點(diǎn)廣播地址。
3.public void setInterface(InetAddress inf):如果當(dāng)前系統(tǒng)有多個(gè)網(wǎng)絡(luò)接口,可以使用次方法指定一個(gè)網(wǎng)絡(luò)接口。
4.public InetAddress getInterface():獲取當(dāng)前的網(wǎng)絡(luò)接口。
5.public void setTimeToLive(int ttl):該參數(shù)設(shè)置數(shù)據(jù)報(bào)最多可以跨過多少個(gè)網(wǎng)絡(luò),當(dāng)ttl為0時(shí),指定數(shù)據(jù)報(bào)應(yīng)停留在本地主機(jī);當(dāng)ttl的值為1時(shí),指定數(shù)據(jù)報(bào)發(fā)送到本地局域網(wǎng);當(dāng)ttl的值為32時(shí),意味著只能發(fā)送到本站點(diǎn)的網(wǎng)絡(luò)上;當(dāng)ttl為64時(shí),意味著數(shù)據(jù)報(bào)應(yīng)保留在本地區(qū);當(dāng)ttl的值為128時(shí),意味著數(shù)據(jù)報(bào)應(yīng)保留在本大洲;當(dāng)ttl為255時(shí),意味著數(shù)據(jù)報(bào)可發(fā)送到所有地方;默認(rèn)情況下,該ttl的值為1。

  2.3 一個(gè)簡單的示例

   接下來我們寫一個(gè)小例子來看一下MulticastSocket的使用方式。這個(gè)程序?qū)瑑蓚€(gè)線程:1. 接收線程,主要用于接收信息;2. 主線程,主要用于發(fā)送信息。將這個(gè)程序部署到局域網(wǎng)上的幾臺(tái)電腦上,全部啟動(dòng),就可以實(shí)現(xiàn)多臺(tái)電腦的組播了,而且每臺(tái)主機(jī)都可以發(fā)出信息,其它主機(jī)則會(huì)收到這條信息。

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 {
        //創(chuàng)建MuticastSocket對象,并監(jiān)聽端口55555
        MulticastSocket socket = new MulticastSocket(55555);
        //加入組:235.235.235.235
        socket.joinGroup(InetAddress.getByName("235.235.235.235"));

        //啟動(dòng)線程-此線程用于接收數(shù)據(jù)報(bào)
        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();

        //獲取本機(jī)IP
        String localIp = InetAddress.getLocalHost().getHostAddress();

        //創(chuàng)建一個(gè)Scanner對象,用于接收控制臺(tái)數(shù)據(jù)
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("【請輸入信息】");
            String msg = sc.next();
            // 獲取當(dāng)前時(shí)間格式化字符串,把IP、時(shí)間,以及要發(fā)送的文本連接在一起
            String time = String.format(" <====> %tF %<tT", new Date());
            msg = localIp + time + "\n" + msg + "\n\n";
            //發(fā)送數(shù)據(jù)報(bào)
            socket.send(new DatagramPacket(msg.getBytes(),
                                            msg.getBytes().length,
                                            InetAddress.getByName("235.235.235.235"),
                                            55555));
        }
    }
}

  通過上面的程序,我們發(fā)現(xiàn),MulticastSocket類的使用和DatagramSocket類基本相同,只是多了一步加入組:joinGroup(),所有加入這個(gè)組的主機(jī)都將會(huì)收到信息。

  三. 基于Swing和MulticastSocket實(shí)現(xiàn)的UDP群聊聊天室

   接下來我們使用Swing為這個(gè)程序制作一個(gè)界面,讓用戶操作起來更加方便。

   這個(gè)程序我們制作了兩個(gè)類:

   1). ChatFrame:這個(gè)類繼承自JFrame,實(shí)現(xiàn)了界面的顯示、布局等相關(guān)功能。

   2). SocketChat:這個(gè)類繼承自ChatFrame,加入了MulticastSocket的連接、信息發(fā)送和接收。

  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();//接收文本框,用來顯示服務(wù)器發(fā)送過來的文本
    private JTextArea sendArea = new JTextArea();//發(fā)送文本框,用來顯示當(dāng)前用戶要發(fā)送的文本

    private JButton sendBtn = new JButton("SEND");//發(fā)送按鍵

    public ChatFrame() {
        this.initFrame();//初始化窗口
        this.initComponent();//初始化組件
        this.initListener();//初始化監(jiān)聽器
        this.receive();//開啟監(jiān)聽服務(wù)器線程,把接收到的文本顯示在receiveArea中
    }

    // 初始化監(jiān)聽器
    private void initListener() {
        // 給發(fā)送按鍵添加監(jiān)聽器,當(dāng)被點(diǎn)擊時(shí)調(diào)用send()方法
        sendBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                send();
            }
        });

        // 給發(fā)送文本框添加鍵盤監(jiān)聽器,當(dāng)按下Ctrl+ENTER時(shí)調(diào)用send()方法
        sendArea.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                if(e.isControlDown()) {
                    if(e.getKeyCode() == KeyEvent.VK_ENTER) {
                        send();
                    }
                }
            }
        });
    }

    // 子類需要重寫本方法
    // 在本方法中使用socket實(shí)現(xiàn)消息發(fā)送
    public abstract void sendText(String text);

    // 子類需要重寫本方法
    // 在本方法中啟動(dòng)監(jiān)聽服務(wù)器線程,調(diào)用本類receiveText(String)把接收到的文本顯示出來
    public abstract void receive();

    // 本方法用來發(fā)送文本
    public void send() {
        // 如果發(fā)送文本框中沒有文本,彈出警告對話框
        if(sendArea.getText().equals("")) {
            javax.swing.JOptionPane.showMessageDialog(this, "空文本不能發(fā)送!");
            sendArea.requestFocus();// 把光標(biāo)歸還給發(fā)送文本框
            return;
        }

        // 調(diào)用子類的方法完成文本發(fā)送
        sendText(sendArea.getText());
        // 把發(fā)送文本框內(nèi)容清空
        sendArea.setText(null);
    }

    // 本方法完成接收服務(wù)器消息的后續(xù)工作-在文本框中顯示服務(wù)器消息,子類的receive()方法在接收服務(wù)器消息后可以調(diào)用本方法
    public void receiveText(String text) {
        receiveArea.append(text);//把接收到的消息添加到文本框中
        // 設(shè)置光標(biāo)位置到最后,如果不設(shè)置滾動(dòng)條不動(dòng)
        receiveArea.setCaretPosition(receiveArea.getText().length());
    }

    // 初始化組件
    private void initComponent() {
        // 使用接收文本框創(chuàng)建滾動(dòng)窗口(把文本框添加到了滾動(dòng)窗口中),總是顯示縱向滾動(dòng)條,永不顯示橫向滾動(dòng)條
        JScrollPane sp1 = new JScrollPane(receiveArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        // 設(shè)置滾動(dòng)窗口大小、位置、無邊框;并把滾動(dòng)窗口添加到主窗口中
        sp1.setSize(606, 350);
        sp1.setLocation(14, 20);
        sp1.setBorder(null);
        this.add(sp1);

        // 設(shè)置接收文本框背景色、不可編輯、自動(dòng)換行
        receiveArea.setBackground(new Color(238, 238, 238));
        receiveArea.setEditable(false);
        receiveArea.setLineWrap(true);

        // 創(chuàng)建發(fā)送文本框的滾動(dòng)窗口,設(shè)置自動(dòng)換行、大小、位置,然后添加到主窗口中
        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);

        // 設(shè)置發(fā)送按鍵的大小、位置,并添加到主窗口中
        sendBtn.setSize(68, 21);
        sendBtn.setLocation(553, 560);
        this.add(sendBtn);

        // 設(shè)置主窗口的標(biāo)題為當(dāng)前IP地址
        try {
            this.setTitle(InetAddress.getLocalHost().getHostAddress());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 初始化主窗口
    private void initFrame() {
        // 設(shè)置主窗口的大小、布局管理器為空、背景色、位置、大小不可改變
        this.setSize(640, 620);
        this.setLayout(null);
        this.setBackground(new Color(246, 246, 247));
        this.setLocation(350, 50);
        this.setResizable(false);

        // 設(shè)置主窗口的“X”按鈕點(diǎn)擊后結(jié)束程序
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    // 顯示主窗口方法
    public void setVisible(boolean b) {
        super.setVisible(b);//調(diào)用父類的顯示方法
        sendArea.requestFocus();//讓發(fā)送文本框得到焦點(diǎn)
    }
}

  這個(gè)類中定義了很多抽象方法,這些抽象方法由子類實(shí)現(xiàn)。

  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實(shí)現(xiàn)了GUI顯示
 * 本類負(fù)責(zé)使用MulticastSocket完成群聊的發(fā)送消息與接收消息
 */
public class SocketChat extends ChatFrame {
    private MulticastSocket socket;//群組Socket

    public SocketChat() throws IOException {
        socket = new MulticastSocket(54321);//創(chuàng)建群組Socket,綁定54321端口
        //加入虛擬IP:235.235.235.235指定的群組中。虛擬IP范圍是:224.0.0.1 和 239.255.255.255
        //加入群組后,就可以接收群組的消息,也可以向群組發(fā)送消息了
        socket.joinGroup(InetAddress.getByName("235.235.235.235"));
    }

    // 發(fā)送消息方法
    public void sendText(String text) {
        try {
            // 獲取IP地址
            String ip = InetAddress.getLocalHost().getHostAddress();
            // 獲取當(dāng)前時(shí)間格式化字符串
            String time = String.format(" <====> %tF %<tT", new Date());
            // 把IP、時(shí)間,以及要發(fā)送的文本連接在一起
            text = ip + time + "\n" + text + "\n\n";
            // 把文本轉(zhuǎn)換成字節(jié)數(shù)組
            byte[] buff = text.getBytes();
            // 使用socket向群組發(fā)送,socket的send()方法需要兩個(gè)參數(shù):DatagramPacket、端口號
            // DatagramPacket表示數(shù)據(jù)包,創(chuàng)建它需要三個(gè)參數(shù):數(shù)據(jù)包的內(nèi)容、數(shù)據(jù)包的字節(jié)數(shù)、要發(fā)送的IP地址
            socket.send(new DatagramPacket(buff, buff.length, InetAddress.getByName("235.235.235.235"), 54321));
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    // 本方法用來接收群組發(fā)送過來的消息
    public void receive() {
        // 創(chuàng)建監(jiān)聽群組消息的線程,并啟動(dòng)它
        new Thread() {
            public void run() {
                // 循環(huán)監(jiān)聽
                while(true) {
                    try {
                        // 創(chuàng)建數(shù)據(jù)包的字節(jié)數(shù)組,大小為1KB
                        byte[] buff = new byte[1024];
                        // 創(chuàng)建數(shù)據(jù)包
                        DatagramPacket dp = new DatagramPacket(buff, buff.length);
                        // 接收群組發(fā)送過來的消息到數(shù)據(jù)包中
                        // 本方法會(huì)阻塞當(dāng)前線程,直到接收到消息為止
                        socket.receive(dp);
                        // 把接收到的消息轉(zhuǎn)換成字符串
                        String text = new String(dp.getData(), 0, dp.getLength());
                        // 調(diào)用父類的方法完成顯示
                        receiveText(text);
                    } catch(Exception e) {}
                }
            }
        }.start();
    }

    public static void main(String[] args) throws IOException {
        SocketChat sc = new SocketChat();
        sc.setVisible(true);
    }
}

  這個(gè)類使用MulticastSocket,使用端口:54321,組播地址:235.235.235.235。當(dāng)用戶在界面按下send按鈕時(shí),會(huì)觸發(fā)sendText()方法發(fā)送數(shù)據(jù);receive()方法用于使用線程接收數(shù)據(jù),它是在父類的構(gòu)造方法中被觸發(fā)啟動(dòng),啟動(dòng)后,使用無限循環(huán)進(jìn)行信息的接收。

  四.結(jié)束語

   這篇文章我們使用MulticastSocket類實(shí)現(xiàn)了組播功能,并使用Swing和MulticastSocket制作了一個(gè)基于UDP的群聊聊天室,希望大家能夠通過本篇文章了解MulticastSocket類的使用,有興趣的朋友可以基于這個(gè)程序,為它添加更多的功能,例如:文件的發(fā)送、接收;表情圖片的發(fā)送、接收等等。后面我還會(huì)為大家?guī)砀喔鼘?shí)用的程序,期待大家來圍觀哦!謝謝大家!!

分享到:
在線咨詢 我要報(bào)名
和我們在線交談!