Java?Socket編程從零到實(shí)戰(zhàn)詳解(完整實(shí)戰(zhàn)案例)
一、Socket基礎(chǔ)概念與工作流程(圖解)
(先理解“打電話”模型,再寫(xiě)代碼)
1. Socket通信核心模型

關(guān)鍵角色:
- 客戶端:主動(dòng)發(fā)起連接(類(lèi)似撥打電話)
- 服務(wù)端:監(jiān)聽(tīng)端口,等待連接(類(lèi)似待機(jī)電話)
- Socket對(duì)象:連接建立后的數(shù)據(jù)傳輸通道(通話線路)
2. 核心流程分解
- 服務(wù)端:創(chuàng)建
ServerSocket→ 綁定端口 → 阻塞等待連接(accept()) - 客戶端:創(chuàng)建
Socket→ 指定服務(wù)端IP和端口 → 發(fā)起連接 - 雙向通信:通過(guò)輸入流(
InputStream)和輸出流(OutputStream)收發(fā)數(shù)據(jù) - 關(guān)閉連接:調(diào)用
close()釋放資源
二、服務(wù)端與客戶端基礎(chǔ)代碼分步解析
(每行代碼加注釋?zhuān)率直乜矗?/p>
1. 服務(wù)端基礎(chǔ)代碼(單線程版)
// 步驟1:創(chuàng)建ServerSocket,綁定端口8080
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服務(wù)端啟動(dòng),等待連接...");
// 步驟2:等待客戶端連接(阻塞方法,直到有客戶端連接)
Socket clientSocket = serverSocket.accept();
System.out.println("客戶端接入:" + clientSocket.getRemoteSocketAddress());
// 步驟3:獲取輸入流(接收客戶端數(shù)據(jù))
InputStream input = clientSocket.getInputStream();
byte[] buffer = new byte[1024];
int len = input.read(buffer); // 讀取數(shù)據(jù)到buffer數(shù)組
String receivedData = new String(buffer, 0, len);
System.out.println("收到消息:" + receivedData);
// 步驟4:發(fā)送響應(yīng)數(shù)據(jù)
OutputStream output = clientSocket.getOutputStream();
output.write("已收到!".getBytes());
// 步驟5:關(guān)閉連接(實(shí)際開(kāi)發(fā)中需在finally塊處理)
clientSocket.close();
serverSocket.close();2. 客戶端基礎(chǔ)代碼
// 步驟1:連接服務(wù)端(IP+端口)
Socket socket = new Socket("127.0.0.1", 8080);
System.out.println("連接服務(wù)端成功!");
// 步驟2:發(fā)送數(shù)據(jù)
OutputStream output = socket.getOutputStream();
output.write("你好,服務(wù)端!".getBytes());
// 步驟3:接收響應(yīng)
InputStream input = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = input.read(buffer);
String response = new String(buffer, 0, len);
System.out.println("服務(wù)端響應(yīng):" + response);
// 步驟4:關(guān)閉連接
socket.close();三、超時(shí)設(shè)置詳解(解決卡死問(wèn)題)
(必學(xué)技能,避免程序無(wú)限等待)
1. 連接超時(shí)(防止無(wú)法連接時(shí)卡死)
Socket socket = new Socket();
// 設(shè)置連接超時(shí)為5秒(單位:毫秒)
socket.connect(new InetSocketAddress("127.0.0.1", 8080), 5000); // - 觸發(fā)場(chǎng)景:服務(wù)端未啟動(dòng)或網(wǎng)絡(luò)不通
- 異常處理:捕獲
SocketTimeoutException提示用戶檢查網(wǎng)絡(luò)
2. 讀取超時(shí)(防止數(shù)據(jù)未到達(dá)時(shí)阻塞)
socket.setSoTimeout(3000); // 設(shè)置讀取超時(shí)3秒
- 作用范圍:
InputStream.read()操作 - 異常處理:超時(shí)后拋出
SocketTimeoutException,可重試或終止
3. 完整超時(shí)處理示例
try (Socket socket = new Socket()) {
// 連接超時(shí)5秒
socket.connect(new InetSocketAddress("127.0.0.1", 8080), 5000);
// 讀取超時(shí)3秒
socket.setSoTimeout(3000);
InputStream input = socket.getInputStream();
// 讀取數(shù)據(jù)...
} catch (SocketTimeoutException e) {
System.err.println("操作超時(shí):" + e.getMessage());
} catch (IOException e) {
System.err.println("連接失?。? + e.getMessage());
}四、心跳機(jī)制實(shí)現(xiàn)(維持長(zhǎng)連接)
(防止長(zhǎng)時(shí)間無(wú)數(shù)據(jù)導(dǎo)致連接斷開(kāi))
1. 心跳包原理
- 作用:定時(shí)發(fā)送空數(shù)據(jù)包,告知對(duì)方連接存活
- 實(shí)現(xiàn)方式:客戶端定時(shí)任務(wù) + 服務(wù)端超時(shí)檢測(cè)
2. 客戶端心跳代碼
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
OutputStream out = socket.getOutputStream();
out.write(0); // 發(fā)送心跳包(內(nèi)容可為任意約定標(biāo)識(shí))
out.flush();
System.out.println("心跳發(fā)送成功");
} catch (IOException e) {
System.err.println("心跳發(fā)送失敗,連接已斷開(kāi)");
timer.cancel(); // 停止定時(shí)任務(wù)
}
}
}, 0, 30000); // 立即啟動(dòng),每30秒執(zhí)行一次 3. 服務(wù)端檢測(cè)心跳
socket.setSoTimeout(45000); // 超時(shí)時(shí)間略大于心跳間隔
try {
InputStream in = socket.getInputStream();
while (true) {
int data = in.read(); // 阻塞等待數(shù)據(jù)
if (data == 0) {
System.out.println("收到心跳包");
}
}
} catch (SocketTimeoutException e) {
System.err.println("心跳超時(shí),連接斷開(kāi)");
socket.close();
}五、完整實(shí)戰(zhàn)案例:帶超時(shí)與心跳的Echo服務(wù)
服務(wù)端代碼(多線程版)
public class EchoServer {
public static void main(String[] args) throws IOException {
ExecutorService pool = Executors.newCachedThreadPool(); // 線程池處理并發(fā)
try (ServerSocket server = new ServerSocket(8080)) {
System.out.println("服務(wù)端啟動(dòng),端口8080");
while (true) {
Socket client = server.accept();
client.setSoTimeout(45000); // 設(shè)置讀取超時(shí)45秒
pool.submit(() -> handleClient(client));
}
}
}
private static void handleClient(Socket client) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream(), true)) {
String input;
while ((input = in.readLine()) != null) {
if ("HEARTBEAT".equals(input)) { // 識(shí)別心跳包
System.out.println("收到心跳包");
continue;
}
out.println("Echo: " + input); // 回顯消息
}
} catch (SocketTimeoutException e) {
System.err.println("客戶端超時(shí)未響應(yīng),連接關(guān)閉");
} catch (IOException e) {
e.printStackTrace();
} finally {
try { client.close(); } catch (IOException e) {}
}
}
}客戶端代碼(帶心跳與超時(shí))
public class EchoClient {
public static void main(String[] args) {
try (Socket socket = new Socket()) {
// 連接超時(shí)5秒
socket.connect(new InetSocketAddress("127.0.0.1", 8080), 5000);
// 讀取超時(shí)3秒
socket.setSoTimeout(3000);
// 啟動(dòng)心跳線程(每30秒一次)
startHeartbeat(socket.getOutputStream());
Scanner scanner = new Scanner(System.in);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {
System.out.print("輸入消息:");
String msg = scanner.nextLine();
out.println(msg); // 發(fā)送消息
System.out.println("服務(wù)端響應(yīng):" + in.readLine());
}
} catch (SocketTimeoutException e) {
System.err.println("操作超時(shí):" + e.getMessage());
} catch (IOException e) {
System.err.println("連接異常:" + e.getMessage());
}
}
private static void startHeartbeat(OutputStream out) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
out.write("HEARTBEAT\n".getBytes()); // 發(fā)送心跳標(biāo)識(shí)
out.flush();
} catch (IOException e) {
timer.cancel();
}
}
}, 0, 30000);
}
}六、常見(jiàn)問(wèn)題與解決方案速查表
| 問(wèn)題現(xiàn)象 | 可能原因 | 解決方案 |
|---|---|---|
| Connection refused | 服務(wù)端未啟動(dòng)或端口錯(cuò)誤 | 檢查服務(wù)端代碼是否運(yùn)行,確認(rèn)端口一致 |
| Read timed out | 網(wǎng)絡(luò)延遲或服務(wù)端未及時(shí)響應(yīng) | 增加超時(shí)時(shí)間或優(yōu)化服務(wù)端代碼 |
| Broken pipe | 連接已關(guān)閉仍嘗試寫(xiě)數(shù)據(jù) | 發(fā)送前檢查socket.isClosed(),捕獲異常后重連 |
| 內(nèi)存泄漏 | 未關(guān)閉Socket或流 | 使用try-with-resources自動(dòng)關(guān)閉資源 |
七、Java Socket核心方法速查表
| 方法名 | 所屬類(lèi) | 功能描述 | 參數(shù)說(shuō)明 | 返回值 | 常見(jiàn)異常 | 使用示例 |
|---|---|---|---|---|---|---|
| ServerSocket(int port) | ServerSocket | 創(chuàng)建服務(wù)端Socket并綁定指定端口 | port:監(jiān)聽(tīng)的端口號(hào)(0-65535) | 無(wú) | BindException(端口被占用) | new ServerSocket(8080); |
| accept() | ServerSocket | 阻塞等待客戶端連接,返回通信用的Socket對(duì)象 | 無(wú) | Socket(客戶端連接對(duì)象) | IOException | Socket client = serverSocket.accept(); |
| close() | ServerSocket | 關(guān)閉服務(wù)端Socket,釋放端口資源 | 無(wú) | 無(wú) | IOException | serverSocket.close(); |
| Socket(String host, int port) | Socket | 客戶端主動(dòng)連接服務(wù)端(構(gòu)造函數(shù)隱式調(diào)用connect()) | host:服務(wù)端IP;port:服務(wù)端端口 | 無(wú) | UnknownHostException, IOException | Socket socket = new Socket("127.0.0.1", 8080); |
| connect(SocketAddress addr, int timeout) | Socket | 顯式連接服務(wù)端,可設(shè)置超時(shí)時(shí)間 | addr:服務(wù)端地址;timeout:超時(shí)毫秒 | 無(wú) | SocketTimeoutException | socket.connect(new InetSocketAddress("127.0.0.1", 8080), 5000); |
| getInputStream() | Socket | 獲取輸入流,用于接收數(shù)據(jù) | 無(wú) | InputStream | IOException | InputStream in = socket.getInputStream(); |
| getOutputStream() | Socket | 獲取輸出流,用于發(fā)送數(shù)據(jù) | 無(wú) | OutputStream | IOException | OutputStream out = socket.getOutputStream(); |
| setSoTimeout(int timeout) | Socket | 設(shè)置讀取超時(shí)時(shí)間(單位:毫秒),超時(shí)后拋出SocketTimeoutException | timeout:超時(shí)時(shí)間(0表示無(wú)限等待) | 無(wú) | SocketException | socket.setSoTimeout(3000); |
| setKeepAlive(boolean on) | Socket | 啟用/禁用TCP保活機(jī)制(默認(rèn)關(guān)閉),自動(dòng)檢測(cè)連接是否存活 | on:true啟用,false禁用 | 無(wú) | SocketException | socket.setKeepAlive(true); |
| shutdownOutput() | Socket | 關(guān)閉輸出流(發(fā)送FIN包),通知對(duì)方數(shù)據(jù)發(fā)送完畢,但不關(guān)閉Socket | 無(wú) | 無(wú) | IOException | socket.shutdownOutput(); |
| close() | Socket | 關(guān)閉Socket連接,釋放資源 | 無(wú) | 無(wú) | IOException | socket.close(); |
| readInt() | DataInputStream | 從輸入流讀取4字節(jié)的int值(常用于解析長(zhǎng)度頭) | 無(wú) | int | EOFException, IOException | int length = new DataInputStream(in).readInt(); |
| writeInt(int v) | DataOutputStream | 向輸出流寫(xiě)入4字節(jié)的int值(常用于發(fā)送長(zhǎng)度頭) | v:要寫(xiě)入的整數(shù)值 | 無(wú) | IOException | new DataOutputStream(out).writeInt(1024); |
到此這篇關(guān)于Java Socket編程從零到實(shí)戰(zhàn)詳解的文章就介紹到這了,更多相關(guān)Java Socket編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決maven中只有Lifecycle而Dependencies和Plugins消失的問(wèn)題
這篇文章主要介紹了maven中只有Lifecycle而Dependencies和Plugins消失的問(wèn)題及解決方法,本文通過(guò)圖文的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-07-07
java布局管理之CardLayout簡(jiǎn)單實(shí)例
這篇文章主要為大家詳細(xì)介紹了java布局管理之CardLayout的簡(jiǎn)單實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
Java數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)之樹(shù)
intellij idea 2021.2 打包并上傳運(yùn)行spring boot項(xiàng)目的詳細(xì)過(guò)程(spring boot 2
Java 實(shí)現(xiàn)LZ78壓縮算法的示例代碼
淺談Arrays.asList() 和ArrayList類(lèi)型區(qū)別

