教育路上
摘要:Java UDP 案例 聊天 程序 ,UDP網絡程序的基本過程。下面將多線程和Swing 的相關知識結合起來實現一個功能完善的聊天程序。。以下是我們為大家整理的,相信大家閱讀完后肯定有了自己的選擇吧。
2022-05-18 16:39網絡推薦
通過上面的講解,大家應該掌握了編寫UDP網絡程序的基本過程。下面將多線程和Swing 的相關知識結合起來實現一個功能完善的聊天程序。 在實現案例之前,首先分析一下功能需求?,F在所要編寫的 UDP 聊天程序主要是想通 過監聽指定的端口號,目標 IP 地址和目標端口號,實現消息的發送和接收功能,并把聊天 內容顯示出來。程序的用戶圖形界面如圖 13-12 所示
在實際開發中,為了使程序條理清晰,通常會將一個大問題分
決,這里為了使大家更容易理解,把聊天程序分為以下步驟來實現:
1.界面實現
對于界面的實現,首先定義一個類 GuiChat 繼承自 JFram 類,類中聲明成員變量,成員變
量包括組成用戶圖形界面的各個組件以及 DatagramSocket 等,如例 13-4 所示。
例 13-4 GuiChat.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.IOException;
import java.net.*;
public class GuiChat extends JFrame {
private static final int DEFAULT_PORT = 8899;
// 把主窗口分為 NORTH、CEMTER 和 SOUTH 三個部分
private JLabel stateLB; // 顯示監聽狀態
private JTextArea centerTextArea; // 顯示聊天記錄
private JPanel southPanel; // 最下面的面板
private JTextArea inputTextArea; // 聊天輸入框
private JPanel bottomPanel; // 放置 IP 輸入框,按鈕等
private JTextField ipTextField; // IP 輸入框
private JTextField remotePortTF; // 端口號輸入框
private JButton sendBT; // 發送按鈕
private JButton clearBT; // 清除聊天記錄按鈕
private DatagramSocket datagramSocket; // 用于后面功能的實現
}
接下來在 GuiChat 中定義一個 setUpUI()方法,實現用戶圖形界面的設置,如例 10-5 所
示。
例 10-5 setUpUl( )方法
private void setUpUI() { // 初始化 Swing 窗口
// 初始化窗口
setTitle("GUI 聊天");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 400); // 設置窗口的大小
setResizable(false); // 窗口大小不可調整
setLocationRelativeTo(null); // 窗口居中
// 窗口的 NORTH 部分
stateLB = new JLabel("當前還未啟動監聽");
stateLB.setHorizontalAlignment(JLabel.RIGHT);
// 窗口的 CENTER 部分
centerTextArea = new JTextArea(); // 聊天記錄顯示區域
centerTextArea.setEditable(false);
centerTextArea.setBackground(new Color(211, 211, 211));
// 窗口的 SOUTH 部分
southPanel = new JPanel(new BorderLayout());
inputTextArea = new JTextArea(5, 20);// 內容輸入區域
bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5));
ipTextField = new JTextField("127.0.0.1", 8);
remotePortTF = new JTextField(String.valueOf(DEFAULT_PORT), 3);
sendBT = new JButton("發送");
clearBT = new JButton("清屏");
bottomPanel.add(ipTextField);
bottomPanel.add(remotePortTF);
bottomPanel.add(sendBT);
bottomPanel.add(clearBT);
southPanel.add(new JScrollPane(inputTextArea), BorderLayout.CENTER);
southPanel.add(bottomPanel, BorderLayout.SOUTH);
// 添加窗口 NORTH、CENTER、SOUTH 部分的組件
add(stateLB, BorderLayout.NORTH);
add(new JScrollPane(centerTextArea), BorderLayout.CENTER);
add(southPanel, BorderLayout.SOUTH);
setVisible(true);
}
在 setUpUI 方法中,使用 Borderlayou 布局管理器將圖形用戶界面分為 NORTH(代碼的 9~10
行)、CENTER(代碼的 12~ 14 行)、SOUTH(代碼的 16~28 行)三部分。NORTH 部分放置一個
JLabel 標簽 stateLB,用來顯示監聽狀態信息;CENTER 部分放置了一個 JTextArea 文本域,用來
顯示聊天內容;SOUTH 部分放置了一個 JPanel 面板 southPanel,在 southPanel 中又放置了一
一個JTextArea 文本域inputTextArea和一個JPanel 面板bottomPanel,inputTextArea 用來顯示
用戶輸入的聊天內容,bottomPanel 中放置四個組件,ipTextField 和 remotePortTF 文本框分
別用來填寫目標計算機的 IP 地址和端口號,sendBT 和 clearBT 按鈕分別用于發送聊天位息和
清除 centerTextArea 文本域中顯示的聊天內容。
2.編寫事件處理器(發送信息)
界面開發完成后,還需要為界面上的按鈕添加監聽事件,在 GuiChat 類中,sendBT 和 clearBT
按鈕的監聽事件封裝在 setListener( )方法中,如例 13-6 所示。
例 13-6 setListener( )方法
private void setListener() {
// 為 sendBT 按鈕添加事件監聽器
sendBT.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 獲取發送的目標 ip 地址和端口號
final String ipAddress = ipTextField.getText();
final String remotePort = remotePortTF.getText();
// 判斷 ip 地址和端口號是否為空
if (ipAddress == null || ipAddress.trim().equals("")
|| remotePort == null || remotePort.trim().equals("")) {
JOptionPane.showMessageDialog(GuiChat.this, "請輸入 IP 地址和端口
號");
return;
}
if (datagramSocket == null || datagramSocket.isClosed()) {
JOptionPane.showMessageDialog(GuiChat.this, "監聽不成功");
return;
}
// 獲得需要發送的內容
String sendContent = inputTextArea.getText();
byte[] buf = sendContent.getBytes();
try {
// 將發送的內容顯示在自己的聊天記錄中
centerTextArea.append("我對 " + ipAddress + ":" + remotePort
+ " 說:\n" + inputTextArea.getText() + "\n\n");
// 添加內容后,使滾動條自動滾動到最底端
centerTextArea.setCaretPosition(centerTextArea.getText()
.length());
// 發送數據
datagramSocket.send(new DatagramPacket(buf, buf.length,
InetAddress.getByName(ipAddress), Integer
.parseInt(remotePort)));
inputTextArea.setText("");
} catch (IOException e1) {
JOptionPane.showMessageDialog(GuiChat.this, "出錯了,發送不成功");
e1.printStackTrace();
}
};
});
// 為 clearBT 按鈕添加事件監聽器
clearBT.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
centerTextArea.setText(""); // 清空聊天記錄的內容
}
});
}
代碼的第 3~38 行為 sendBT 按鈕添加點擊事件監聽器,在監聽器中首先分別從文本框
ipTetField 和 remotePort 中獲取目標 IP 和端口號,判斷它們是否為 null 或者空字符串"",若
不為空,則從 inputTextArea 中獲取要發送的數據,把數據、IP 地址和端口號封裝到
DatagramPacket 數據包,通過 datagramSocket 的 send () 方法發送,同時在 centerTextArea 中
顯示出發送的內容。
代碼的第 40~44 行為 clearBT 按鈕添加點擊事件監聽器,當點擊 clearBT 按鈕時,會將
centerTextArea 文本域中顯示的聊天內容清空。
3.DatagramSocket 的啟動監聽(接收信息)
UDP 通信時,需要使用 DatagramSocket 啟動監聽,DatagramSocket 監聽的代碼定
義在 GuiChat 類的 initSocket()方法中,initSocket()方法需要實現兩個功脂第一個是接收用戶填
寫程序監聽的端口號,如例 13-7 所示。
例 13-7 DatagramSocket 的啟動監聽
private void initSocket() {
int port = DEFAULT_PORT;
while (true) {
try {
if (datagramSocket != null && !datagramSocket.isClosed()) {
datagramSocket.close();
}
try { // 判斷端口號是否在 1-65535 之間
port = Integer.parseInt(JOptionPane.showInputDialog(this,
"請輸入端口號", "端口號",
JOptionPane.QUESTION_MESSAGE));
if (port < 1 || port > 65535) {
throw new RuntimeException("端口號超出范圍");
}
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"你輸入的端口不正確,請輸入 1-65535 之間的數");
continue; // 端口不正確重新填寫
}
// 啟動 DatagramSocket
datagramSocket = new DatagramSocket(port);
startListen(); // 調用 startListen 方法
// 在 stateLB 中顯示程序監聽的端口號
stateLB.setText("已在 " + port + " 端口監聽");
break;
} catch (SocketException e) { // 端口號被占用重新填寫
JOptionPane.showMessageDialog(this, "端口已被占用,請重新設置端
口");
stateLB.setText("當前還未啟動監聽");
}
}
}
例 13—7 中,initSocket( )方法使用了 while(true)循環,目的是為了當用戶填寫的
監聽口號不滿足要求時,反復地彈出輸入窗口,讓用戶重新輸人端口號,其過程如下:
首先彈一個輸入窗口,接收用戶輸入的監聽端口號,如果端口號不在 1~65535 之中,會
拋出一個異常,在 catch()塊中捕獲異常后,會彈出窗口提示"你輸人的端口不正確,
請輸人 1-655353 之間的數"的信息,同時執行 continue 語句,重新開始循環,如果用
戶輸人的監聽端口號在 1~65535 之間,就使用該端口號創建 DatagramSocket 對象。本
節在"腳下留心"中講過,如果 DatagramSocke 監聽的端口號被其他程序占用,也會拋
出一個異常,在 25~28 行的 catch()代碼塊中會捕獲這個異常,并彈出窗口提示"端口
已被占用,請重新設置端口"的信息,然后繼續執行循環。只有填寫的監聽端口號正確
并且未被占用時,才會成功創建 DatagramSocket 對象,之后調用 startListen()方法,
執行 break 語句退出 while 循環。
initSocket()方法還有一個功能是接收消息,這個功能封裝在 startListen()方法中,
為了避免在接收消息時 AWT 線程發生阻塞,需要在 startListen()方法中開啟一個新的
線程,把接收消息的實現放在新線程的 run()方法中,如例 13-8 所示。
例 13-8 startListen()方法
private void startListen() {
new Thread() {
private DatagramPacket p;
public void run() {
byte[] buf = new byte[1024];
// 創建 DatagramPacket
p = new DatagramPacket(buf, buf.length);
while (!datagramSocket.isClosed()) {
try {
datagramSocket.receive(p); // 接收聊天消息
// 添加到聊天記錄框
centerTextArea.append(p.getAddress().getHostAddress()
+ ":"
+ ((InetSocketAddress) p.getSocketAddress())
.getPort() + " 對我說:\n"
+ new String(p.getData(), 0, p.getLength())
+ "\n\n");
// 使滾動條自動滾動到最底端
centerTextArea.setCaretPosition(centerTextArea
.getText().length());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
例 13-8 中,startListen()方法很簡單,在該方法中開啟了一個新的線程,在 run()方法中使
用 while 循環,判斷 DatagramSocket 對象有沒有關閉,如果沒有關閉就不停地接收消息,并
顯示在 centerTextArea 文本域中。
4.功能測試
至此,所有的功能模塊都已實現,在 GuiChat 的構造方法中分別調用 setUpUl()、
initSocket()與 setListener()方法,就完成了 UDP 聊天程序,如例 13-9 所示。
例 13-9 GuiChat 的構造方法
public GuiChat() {
setUpUI();
initSocket();
setListener();
}
public static void main(String[] args) {
new GuiChat();
}
例 13-9 中,在 GuiChat 類的 main()方法中創建 GuiChat 實例對象,就可以運行這個 UDP 聊
天程序。這里啟動兩個聊天程序的窗口,分別填寫對方的 IP 地址和監聽的端口號,測試程
序的聊天功能,如圖 13-13 所示。
由于兩個聊天程序都在本機啟動,從圖 13-13 中可以看到,IP 地址填寫的都是本地回環 地址 127.0.0.1,而且只要保證發送端填寫的目標端口號和接收端口號一致,就能實現兩個 窗口正常的聊天功能。
訪客的評論 2023/11/08 20:52
文中描述的是準確的嗎,如何報名!