Java?Socket編程從零到實(shí)戰(zhàn)詳解(完整實(shí)戰(zhàn)案例)
一、Socket基礎(chǔ)概念與工作流程(圖解)
(先理解“打電話”模型,再寫(xiě)代碼)
1. Socket通信核心模型
關(guān)鍵角色:
- 客戶(hù)端:主動(dòng)發(fā)起連接(類(lèi)似撥打電話)
- 服務(wù)端:監(jiān)聽(tīng)端口,等待連接(類(lèi)似待機(jī)電話)
- Socket對(duì)象:連接建立后的數(shù)據(jù)傳輸通道(通話線路)
2. 核心流程分解
- 服務(wù)端:創(chuàng)建
ServerSocket
→ 綁定端口 → 阻塞等待連接(accept()
) - 客戶(hù)端:創(chuàng)建
Socket
→ 指定服務(wù)端IP和端口 → 發(fā)起連接 - 雙向通信:通過(guò)輸入流(
InputStream
)和輸出流(OutputStream
)收發(fā)數(shù)據(jù) - 關(guān)閉連接:調(diào)用
close()
釋放資源
二、服務(wù)端與客戶(hù)端基礎(chǔ)代碼分步解析
(每行代碼加注釋?zhuān)率直乜矗?/p>
1. 服務(wù)端基礎(chǔ)代碼(單線程版)
// 步驟1:創(chuàng)建ServerSocket,綁定端口8080 ServerSocket serverSocket = new ServerSocket(8080); System.out.println("服務(wù)端啟動(dòng),等待連接..."); // 步驟2:等待客戶(hù)端連接(阻塞方法,直到有客戶(hù)端連接) Socket clientSocket = serverSocket.accept(); System.out.println("客戶(hù)端接入:" + clientSocket.getRemoteSocketAddress()); // 步驟3:獲取輸入流(接收客戶(hù)端數(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. 客戶(hù)端基礎(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
提示用戶(hù)檢查網(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)方式:客戶(hù)端定時(shí)任務(wù) + 服務(wù)端超時(shí)檢測(cè)
2. 客戶(hù)端心跳代碼
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("客戶(hù)端超時(shí)未響應(yīng),連接關(guān)閉"); } catch (IOException e) { e.printStackTrace(); } finally { try { client.close(); } catch (IOException e) {} } } }
客戶(hù)端代碼(帶心跳與超時(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 | 阻塞等待客戶(hù)端連接,返回通信用的Socket對(duì)象 | 無(wú) | Socket (客戶(hù)端連接對(duì)象) | IOException | Socket client = serverSocket.accept(); |
close() | ServerSocket | 關(guān)閉服務(wù)端Socket,釋放端口資源 | 無(wú) | 無(wú) | IOException | serverSocket.close(); |
Socket(String host, int port) | Socket | 客戶(hù)端主動(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)文章希望大家以后多多支持腳本之家!
- java實(shí)現(xiàn)基于UDP協(xié)議網(wǎng)絡(luò)Socket編程(C/S通信)
- Java編程實(shí)現(xiàn)基于TCP協(xié)議的Socket聊天室示例
- Java Socket編程心跳包創(chuàng)建實(shí)例解析
- Java Socket編程服務(wù)器響應(yīng)客戶(hù)端實(shí)例代碼
- Java編程Socket實(shí)現(xiàn)多個(gè)客戶(hù)端連接同一個(gè)服務(wù)端代碼
- Java編程利用socket多線程訪問(wèn)服務(wù)器文件代碼示例
- Java多線程編程實(shí)現(xiàn)socket通信示例代碼
相關(guān)文章
解決maven中只有Lifecycle而Dependencies和Plugins消失的問(wèn)題
這篇文章主要介紹了maven中只有Lifecycle而Dependencies和Plugins消失的問(wèn)題及解決方法,本文通過(guò)圖文的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-07-07java布局管理之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ū)別