教育行業(yè)A股IPO第一股(股票代碼 003032)

全國咨詢/投訴熱線:400-618-4000

使用UDP實現(xiàn)群聊聊天室

更新時間:2022年11月15日14時27分 來源:傳智教育 瀏覽次數(shù):

好口碑IT培訓

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

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

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

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

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

   4). 結(jié)束語:

  一、組播的概念

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

  1.1 單播:

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

  1.2 廣播:

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

  1.3 組播:

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

  二. MulticastSocket類的使用

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

1663291462281_1.jpg

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

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

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

  2.2 成員方法說明

   以下是幾個比較重要的成員方法:

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

  2.3 一個簡單的示例

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

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"));

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

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

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

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

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

   這個程序我們制作了兩個類:

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

   2). SocketChat:這個類繼承自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();//接收文本框,用來顯示服務器發(fā)送過來的文本
    private JTextArea sendArea = new JTextArea();//發(fā)送文本框,用來顯示當前用戶要發(fā)送的文本

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

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

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

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

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

    // 子類需要重寫本方法
    // 在本方法中啟動監(jiān)聽服務器線程,調(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();// 把光標歸還給發(fā)送文本框
            return;
        }

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

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

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

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

        // 創(chuàng)建發(fā)送文本框的滾動窗口,設(shè)置自動換行、大小、位置,然后添加到主窗口中
        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è)置主窗口的標題為當前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”按鈕點擊后結(jié)束程序
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

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

  這個類中定義了很多抽象方法,這些抽象方法由子類實現(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實現(xiàn)了GUI顯示
 * 本類負責使用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();
            // 獲取當前時間格式化字符串
            String time = String.format(" <====> %tF %<tT", new Date());
            // 把IP、時間,以及要發(fā)送的文本連接在一起
            text = ip + time + "\n" + text + "\n\n";
            // 把文本轉(zhuǎn)換成字節(jié)數(shù)組
            byte[] buff = text.getBytes();
            // 使用socket向群組發(fā)送,socket的send()方法需要兩個參數(shù):DatagramPacket、端口號
            // DatagramPacket表示數(shù)據(jù)包,創(chuàng)建它需要三個參數(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)聽群組消息的線程,并啟動它
        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ù)包中
                        // 本方法會阻塞當前線程,直到接收到消息為止
                        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);
    }
}

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

  四.結(jié)束語

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

0 分享到:
和我們在線交談!