Socket 是網絡編程基石。本文通過一個基於 Swing GUI 的單聊天室,演示 Java 阻塞 IO(BIO)模式下的服務端/客戶端雙向通信全流程。代碼結構清晰,適合網絡編程入門教學與快速原型驗證。
\n 為邊界,PrintWriter.println() / BufferedReader.readLine() 自動處理。package qiu;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.Calendar;
public class MyServer extends JFrame implements ActionListener {
// GUI 組件
private JTextArea area = new JTextArea();
private JTextField input = new JTextField(20);
private JButton sendBtn = new JButton("發送");
private JScrollPane scroll = new JScrollPane(area);
private JPanel bottom = new JPanel();
// 網絡
private PrintWriter out; // 發送給客戶端
private BufferedReader in; // 接收客戶端
public static void main(String[] args) {
SwingUtilities.invokeLater(MyServer::new);
}
public MyServer() {
initGUI();
startServer();
}
private void initGUI() {
area.setEditable(false);
sendBtn.addActionListener(this);
sendBtn.setActionCommand("send");
bottom.add(input);
bottom.add(sendBtn);
add(scroll, BorderLayout.CENTER);
add(bottom, BorderLayout.SOUTH);
setTitle("聊天服務端");
setSize(400, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
private void startServer() {
new Thread(() -> {
try (ServerSocket ss = new ServerSocket(9988)) {
append("服務端啟動,等待客戶端連接...端口 9988");
Socket socket = ss.accept(); // 阻塞等待
append("客戶端已連接: " + socket.getInetAddress());
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
String msg;
while ((msg = in.readLine()) != null) {
append("客戶端 " + now() + "\n" + msg);
}
append("客戶端斷開連接");
} catch (IOException e) {
append("服務端異常: " + e.getMessage());
e.printStackTrace();
}
}).start();
}
@Override
public void actionPerformed(ActionEvent e) {
if ("send".equals(e.getActionCommand())) {
String msg = input.getText().trim();
if (!msg.isEmpty() && out != null) {
out.println(msg);
append("服務端 " + now() + "\n" + msg);
input.setText("");
}
}
}
private void append(String text) {
SwingUtilities.invokeLater(() -> {
area.append(text + "\n");
area.setCaretPosition(area.getDocument().getLength());
});
}
private String now() {
Calendar c = Calendar.getInstance();
return String.format("%02d:%02d:%02d",
c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
}
}
package qiu;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.Calendar;
public class MyClient extends JFrame implements ActionListener {
private JTextArea area = new JTextArea();
private JTextField input = new JTextField(20);
private JButton sendBtn = new JButton("發送");
private JScrollPane scroll = new JScrollPane(area);
private JPanel bottom = new JPanel();
private PrintWriter out;
private BufferedReader in;
public static void main(String[] args) {
SwingUtilities.invokeLater(MyClient::new);
}
public MyClient() {
initGUI();
connectServer();
}
private void initGUI() {
area.setEditable(false);
sendBtn.addActionListener(this);
sendBtn.setActionCommand("send");
bottom.add(input);
bottom.add(sendBtn);
add(scroll, BorderLayout.CENTER);
add(bottom, BorderLayout.SOUTH);
setTitle("聊天客戶端");
setSize(400, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
private void connectServer() {
new Thread(() -> {
try {
Socket socket = new Socket("127.0.0.1", 9988);
append("已連接到服務端");
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
String msg;
while ((msg = in.readLine()) != null) {
append("服務端 " + now() + "\n" + msg);
}
append("服務端斷開連接");
} catch (IOException e) {
append("連接異常: " + e.getMessage());
e.printStackTrace();
}
}).start();
}
@Override
public void actionPerformed(ActionEvent e) {
if ("send".equals(e.getActionCommand())) {
String msg = input.getText().trim();
if (!msg.isEmpty() && out != null) {
out.println(msg);
append("客戶端 " + now() + "\n" + msg);
input.setText("");
}
}
}
private void append(String text) {
SwingUtilities.invokeLater(() -> area.append(text + "\n"));
}
private String now() {
Calendar c = Calendar.getInstance();
return String.format("%02d:%02d:%02d",
c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
}
}
javac qiu/MyServer.java qiu/MyClient.javajava qiu.MyServerjava qiu.MyClient| 點 | 說明 |
|---|---|
| 字符編碼 | 顯式指定 UTF-8,避免中文亂碼 |
| 自動刷新 | PrintWriter(..., true) 開啟 autoFlush,println 即發送 |
| 線程分離 | 網絡 IO 放入獨立線程,避免阻塞 EDT 導致界面凍結 |
| 線程安全更新 UI | 所有 area.append 包裝在 SwingUtilities.invokeLater |
| 資源關閉 | 使用 try-with-resources 管理 ServerSocket/Socket/流 |
| 行協議 | readLine() 遇到 \n/\r\n 返回,適合文本聊天 |
ss.accept() 只接受一個,後續連接被忽略。while(true){ new Thread(new ClientHandler(ss.accept())).start(); },維護 Map<String, PrintWriter> 廣播/私聊。{"type":"chat","from":"A","to":"B","msg":"..."},使用 Jackson/Gson 序列化。LengthFieldBasedFrameDecoder 解決粘包。{"type":"ping"},服務端超時移除失效連接。本示例以最小可行代碼展示了 Java Socket 雙向通信核心:ServerSocket.accept()、Socket.getInputStream/OutputStream、BufferedReader/PrintWriter 行文本處理、Swing 線程分離與安全更新。理解這套基礎模型,是進階學習 Netty、WebSocket、gRPC 等高性能網絡框架的必經之路。