更新時間:2022-09-16 來源:黑馬程序員 瀏覽量:
大家好,今天為大家帶來了一個非常有意思的小程序——UDP實現的群聊聊天室。這個程序使用的UDP協議,并使用DatagramSocket的子類MulticastSocket實現組播,可以部署在一個局域網內的多臺電腦上,并可以實現文字群聊。
本文將會按照以下幾個小節講解:
1). 組播的概念:**這個小節我們將講解什么是:單播、廣播、組播。
2). MulticastSocket類的使用:**這個小節我們將講解MulticastSocket類的基本使用,并實現控制臺的信息收發。
3). 基于Swing和MulticastSocket實現的UDP群聊聊天室:**這個小節我們將制作一個界面,并結合MulticastSocket類實現一個完整的UDP群聊聊天室。
4). 結束語:
網絡數據傳播按照接收者的數量,可分為以下3種方式:
單播是指實現“點對點”的通信,發送者發送數據要發送給網絡上的唯一的一臺電腦,指定一個接收者。像TCP協議和UDP協議都能實現點對點通信。
發送者發送的數據可以被某個接收范圍內所有的接收者接收。它類似于廣播電臺,向某個范圍內的所有用戶發送廣播信號,接收人打開廣播就可以聽到,關閉廣播設備就停止收聽。由于廣播會大大增加網絡數據流量,所以通常情況下一些網絡路由器會禁止廣播數據,尤其是一些占用網絡資源比較大的視頻數據等。
組播是指發送的數據可以被指定的一組用戶接收。組播的范圍沒有廣播那么廣,任何的一臺電腦都可以隨時加入某一個組接收組播數據。若要使用組播,則需要讓一個數據報標有一組目標主機地址,當數據報發出后,整個組的所有主機都能收到該數據報。IP協議為組播提供了這批特殊的IP地址,這些IP地址的范圍是224.0.0.0至239.255.255.255。在Java類庫中,DatagramSocket有一個子類:MulticastSocket,它具有組播的功能,它可以與DatagramPackage結合使用,用于發送和接收組播包。
Java類庫中MulticastSocket類可以實現組播功能,它是DatagramSocket的子類:
通過API文檔我們可以看到它有三個構造方法:
1. MulticastSocket() 創建一個多播套接字。(使用隨機端口,如果只發送,可以使用這個構造方法) 2. MulticastSocket(int port) 創建一個多播套接字并將其綁定到一個特定的端口。(如果需要發送和接收,需要使用這個構造方法) 3. MulticastSocket(SocketAddress bindaddr) 創建一個多播套接字綁定到指定的套接字地址。
以下是幾個比較重要的成員方法:
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。
接下來我們寫一個小例子來看一下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 %<tT", 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)); } } }
通過上面的程序,我們發現,MulticastSocket類的使用和DatagramSocket類基本相同,只是多了一步加入組:joinGroup(),所有加入這個組的主機都將會收到信息。
接下來我們使用Swing為這個程序制作一個界面,讓用戶操作起來更加方便。
這個程序我們制作了兩個類:
1). ChatFrame:這個類繼承自JFrame,實現了界面的顯示、布局等相關功能。
2). SocketChat:這個類繼承自ChatFrame,加入了MulticastSocket的連接、信息發送和接收。
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();//讓發送文本框得到焦點 } }
這個類中定義了很多抽象方法,這些抽象方法由子類實現。
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 %<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); } }
這個類使用MulticastSocket,使用端口:54321,組播地址:235.235.235.235。當用戶在界面按下send按鈕時,會觸發sendText()方法發送數據;receive()方法用于使用線程接收數據,它是在父類的構造方法中被觸發啟動,啟動后,使用無限循環進行信息的接收。
這篇文章我們使用MulticastSocket類實現了組播功能,并使用Swing和MulticastSocket制作了一個基于UDP的群聊聊天室,希望大家能夠通過本篇文章了解MulticastSocket類的使用,有興趣的朋友可以基于這個程序,為它添加更多的功能,例如:文件的發送、接收;表情圖片的發送、接收等等。后面我還會為大家帶來更多更實用的程序,期待大家來圍觀哦!謝謝大家!!