採用經典 Client/Server (C/S) 架構 搭配 多執行緒 實現廣播訊息傳遞:
ChatHandler),Client 以單一執行緒接收訊息public class ChatServer extends JFrame {
private TextArea output = new TextArea(15, 40);
private JMenuBar menuBar = new JMenuBar();
public ChatServer(String title) {
// 選單、關於對話框、視窗事件處理...
output.setEditable(false);
output.setForeground(Color.BLUE);
setJMenuBar(menuBar);
getContentPane().add("North", output);
getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER));
}
public static void main(String[] args) throws IOException {
if (args.length != 1) throw new IllegalArgumentException("Syntax: ChatServer ");
int port = Integer.parseInt(args[0]);
ChatServer win = new ChatServer("ChitChat Broadcast Messenger: Server");
// 置中顯示...
win.setVisible(true);
ServerSocket server = new ServerSocket(port);
while (true) {
Socket client = server.accept(); // 阻塞等待連線
output.append("\nAccepted from " + client.getInetAddress() + "\n");
new ChatHandler(client).start(); // 啟動處理執行緒
}
}
}
class ChatHandler extends Thread {
private Socket client;
private static List writers = Collections.synchronizedList(new ArrayList<>());
public ChatHandler(Socket socket) { this.client = socket; }
@Override
public void run() {
try (DataInputStream in = new DataInputStream(client.getInputStream());
DataOutputStream out = new DataOutputStream(client.getOutputStream())) {
writers.add(out); // 註冊寫入器
String msg;
while ((msg = in.readUTF()) != null) {
broadcast(msg); // 廣播給所有 Client
}
} catch (IOException e) { /* 移除 writer、關閉資源 */ }
}
private void broadcast(String msg) {
for (DataOutputStream w : writers) {
try { w.writeUTF(msg); w.flush(); } catch (IOException ignored) {}
}
}
}
public class ChatClient implements Runnable, WindowListener, ActionListener {
private Socket socket;
private DataInputStream in;
private DataOutputStream out;
private Thread listener;
private TextArea output;
private TextField input;
private SketchFrame window; // 自訂視窗類別
public ChatClient(String host, int port, SketchFrame window) {
this.window = window;
// GUI 初始化:output 置中、input 置底、選單、顏色選擇...
}
public synchronized void start() throws IOException {
socket = new Socket(host, port);
in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
out.writeUTF(yourName + " has logged on\n");
listener = new Thread(this);
listener.start();
window.setVisible(true);
}
@Override
public void run() { // 接收訊息迴圈
try {
while (!Thread.interrupted()) {
String line = in.readUTF();
output.append(line + "\n");
}
} catch (IOException e) { handleIOException(e); }
}
public void actionPerformed(ActionEvent e) { // 發送訊息
String msg = input.getText();
if (msg.length() > 0) {
try { out.writeUTF(msg); out.flush(); input.setText(""); }
catch (IOException ex) { handleIOException(ex); }
}
}
public synchronized void stop() throws IOException {
if (listener != null) {
listener.interrupt();
listener = null;
out.close();
}
}
}
| 技術點 | 說明 |
|---|---|
| 阻塞式 I/O | readUTF()、accept() 預設阻塞,適合簡單應用 |
| 執行緒安全 | 共享 writers 列表使用 Collections.synchronizedList |
| 資源釋放 | stop() 中關閉串流、中斷執行緒、移除 writer |
| GUI 更新 | Client 接收訊息直接在接收執行緒更新 TextArea(教學用途可接受,生產環境建議 SwingUtilities.invokeLater) |