目錄
1. 網絡編程基礎:搞懂設備通信的底層邏輯
1.1?為啥需要網絡編程?—— 讓設備 “互通有無”
1.2 什么是網絡編程?—— 給數據 “定規矩、找路線”
1.3?網絡編程的基本概念:理清通信里的角色和流程
1.3.1?發送端和接收端 —— 數據的 “發信人” 和 “收信人”
1.3.2 請求和響應 —— 通信的 “一問一答”?
1.3.3?客戶端和服務端 —— 穩定的 “需求方” 和 “服務方”?
1.3.4 常見的客戶端服務端模型
2. Socket 套接字:網絡通信的 “連接器”?
2.1?概念:網絡通信的 “電話”
2.2?分類:兩種通信 “風格
2.2.1?數據報套接字
2.2.2?流套接字
2.3?Java數據報套接字通信模型
2.4?Java流套接字通信模型
2.5 Socket 編程注意事項:避坑要點?
3. UDP 數據報套接字編程:簡單高效的 “快傳” 實踐?
3.1?API 介紹:核心工具
3.1.1 DatagramSocket
3.1.2 DatagramPacket?
3.1.3?InetSocketAddress
3.2 代碼示例:UDP 通信全流程?
3.2.1 UDP Echo Server
3.2.2?UDP Echo Client
3.2.3?3. UDP Dict Serve 字典服務器
4.?TCP 流套接字編程:可靠傳輸的 “保障”
4.1?API 介紹:構建可靠連接
4.1.1?ServerSocket
4.1.2?Socket
?編輯4.2?代碼示例:TCP 通信實踐
4.2.1 TCP Echo Server
4.2.2?TCP Echo Client
5. 長短連接:按需選擇通信模式
5.1 短連接與長連接的定義
5.2 短連接與長連接的核心區別
5.2.1 連接建立與關閉的耗時差異
5.2.2 主動請求的發起方差異
5.2.3 適用場景差異
5.3 長連接的 “擴展痛點” 與優化方向
5.3.1 BIO 長連接的資源瓶頸
5.3.2 NIO 優化長連接的思路
5.4 總結:按需選型,平衡效率與資源
????????網絡編程聽著高深,其實就是解決 “設備之間咋傳數據” 的問題。想象一下,你手機刷短視頻,本質是手機(客戶端)和短視頻服務器(服務端)在傳數據;玩聯機游戲,是你電腦和游戲服務器、隊友設備在傳數據。這篇就把網絡編程最基礎的邏輯和核心工具 Socket,掰開揉碎了講,保證像嘮家常一樣好懂 。
1. 網絡編程基礎:搞懂設備通信的底層邏輯
1.1?為啥需要網絡編程?—— 讓設備 “互通有無”
????????用手機點外賣,手機得把 “我要點宮保雞丁” 的需求發給外賣平臺服務器;玩聯機游戲,你操控角色的動作得傳給隊友的設備;智能手表測的心率數據,得傳到手機 App 上顯示……網絡編程就是讓這些 “需求、動作、數據”,能在不同設備(或同一設備的不同程序)之間準確、高效傳遞的技術。
簡單說:沒有網絡編程,所有跨設備的功能全廢!手機只能當計算器,電腦連不上網頁,智能設備數據也傳不出去,所有的網絡資源都無法訪問,世界直接 “斷網癱瘓” 。
- 所謂的網絡資源,其實就是在網絡中可以獲取的各種數據資源。
- 而所有的網絡資源,都是通過網絡編程來進行數據傳輸的。
1.2 什么是網絡編程?—— 給數據 “定規矩、找路線”
????????網絡編程,指網絡上的主機,通過不同的進程,以編程的方式實現網絡通信(或稱為網絡數據傳輸)。
????????當然,我們只要滿足進程不同就行;所以即便是同一個主機,只要是不同進程,基于網絡來傳輸數據,也屬于網絡編程。?
????????特殊的,對于開發來說,在條件有限的情況下,一般也都是在一個主機中運行多個進程來完成網絡編程。
????????但是,我們一定要明確,我們的目的是提供網絡上不同主機,基于網絡來傳輸數據資源:
- 進程A:編程來獲取網絡資源
- 進程B:編程來提供網絡資源
????????網絡編程的核心,就是控制程序按照 “網絡協議”(比如 TCP、UDP),把數據打包、發送、接收、解析。舉個例子:你用微信發消息 “吃了嗎”,手機里的微信程序會:
- 打包:把文字轉成符合網絡協議的 “數據包”(類似把信裝進信封,寫上收件人地址);
- 發送:通過網絡(WiFi、基站)把數據包傳輸出去(類似快遞小哥把信運到目的地);
- 接收:對方的微信程序收到數據包(類似收件人拿到信);
- 解析:把數據包還原成 “吃了嗎” 的文字(類似拆信封讀內容)。
????????整個過程,就是網絡編程在 “暗中操作”,讓數據能跨設備 “跑來跑去”。
1.3?網絡編程的基本概念:理清通信里的角色和流程
????????這些概念看著抽象,對應生活場景就秒懂了,一個個看:
1.3.1?發送端和接收端 —— 數據的 “發信人” 和 “收信人”
- 發送端:數據的發送方進程,稱為發送端,它是主動發數據的一方。比如你給朋友發微信,你手機就是發送端;游戲里你開槍,你的設備就是發送端(把 “開槍動作” 發出去)。發送端主機即網絡通信的源主機。
- 接收端:數據的接收方進程,稱為接收端,它是被動收數據的一方。朋友的手機收你的微信、隊友的設備收你 “開槍動作”,它們就是接收端。接收端主機即網絡通信中的目的主機。
- 收發端:發送端和接收端兩端,也簡稱為收發端
注意:發送端和接收端只是相對的,角色會切換!只是一次網絡數據傳輸產生數據流向后的概念。比如你和朋友互相發消息,你們的手機會輪流當 “發送端” 和 “接收端”,像打乒乓球一樣來回傳數據。
1.3.2 請求和響應 —— 通信的 “一問一答”?
????????一般來說,獲取一個網絡資源,涉及到兩次網絡數據傳輸:
? ? ? ? ? ? ? ? ?? 第一次:請求數據的發送。
?????????????????? 第二次:響應數據的發送。
請求(Request):發送端主動提的 “需求”。比如你打開抖音,抖音 App 會給服務器發 “請求”:“給我推薦點搞笑視頻”;你登錄微信,微信 App 會發 “請求”:“驗證這個賬號密碼對不對”。
響應(Response):接收端針對請求的 “回復”。服務器收到抖音的請求,回復 “這是搞笑視頻列表”;收到微信登錄請求,回復 “密碼正確,登錄成功”(或 “密碼錯誤,失敗” )
生活 analogy:你去餐廳(客戶端)喊 “來份牛肉面”(請求),服務員(服務端)回復 “好的,馬上做”(響應),就是典型的 “請求 - 響應”。
1.3.3?客戶端和服務端 —— 穩定的 “需求方” 和 “服務方”?
- 客戶端(Client):主動找別人獲取服務的程序 / 設備。手機 App(抖音、微信)、電腦上的瀏覽器(Chrome、Edge)、智能手表的 App,都是客戶端。特點是 “按需連接”:需要服務時才主動連服務器,不用服務時就 “躺平”。
- 服務端(Server):長期在線、專門給別人提供服務的程序 / 設備。抖音的后臺服務器、微信的認證服務器、游戲的對戰服務器,都是服務端。特點是 “7×24 小時待命”:不管有沒有客戶端找它,它都開著提供服務,隨時準備響應請求。
再舉個生活例子:你用美團 App(客戶端)點外賣,美團的服務器(服務端)負責接收訂單、分配騎手,就是 “客戶端 - 服務端” 的典型交互。
1.3.4 常見的客戶端服務端模型
最常見的場景,客戶端是指給用戶使用的程序,服務端是提供用戶服務的程序:
- 客戶端先發送請求到服務端
- 服務端根據請求數據,執行相應的業務處理
- 服務端返回響應:發送業務處理結果
- 客戶端根據響應數據,展示處理結果(展示獲取的資源,或提示保存資源的處理結果)
- C/S 模型(客戶端 / 服務端 ):需安裝專門客戶端軟件(如微信 App )。優點是功能定制強,能利用本地資源;缺點是客戶端需手動更新 。
- B/S 模型(瀏覽器 / 服務端 ):通過瀏覽器訪問(如知乎網頁版 )。優點是使用方便、跨設備易訪問;缺點是受瀏覽器功能限制,復雜交互體驗弱于 C/S 。
2. Socket 套接字:網絡通信的 “連接器”?
2.1?概念:網絡通信的 “電話”
????????Socket套接字,是由系統提供用于網絡通信的技術,是基于TCP/IP協議的網絡通信的基本操作單元,是程序實現網絡通信的基礎載體。基于Socket套接字的網絡程序開發就是網絡編程。把它想象成 “網絡電話”。程序通過 Socket 建立與其他設備的連接,在連接上發送和接收數據,就像通過電話線路實現雙方通話。無論是 UDP 還是 TCP 通信,都得依靠 Socket 搭建數據傳輸的通道 。
2.2?分類:兩種通信 “風格
2.2.1?數據報套接字
????????使用傳輸層UDP協議,UDP,即User Datagram Protocol(用戶數據報協議),傳輸層協議。類似 “發短信”,發送端把數據打包成 “數據報” 直接發,不管接收端狀態。
優點:傳輸速度快,無需建立連接開銷;
缺點:可能丟數據、數據無序。適合實時性要求高、容忍少量丟包的場景(如在線視頻直播、游戲實時位置同步 )
- 無連接
- 不可靠傳輸
- 面向數據報
- 有接收緩沖區,無發送緩沖區
- 大小受限:一次最多傳輸64k
????????對于數據報來說,可以簡單的理解為,傳輸數據是一塊一塊的,發送一塊數據假如100個字節,必須一次發送,接收也必須一次接收100個字節,而不能分100次,每次接收1個字節。
2.2.2?流套接字
????????使用傳輸層TCP協議,TCP,即Transmission Control Protocol(傳輸控制協議),傳輸層協議。如同 “打電話”,通信前需三次握手建立連接,保證數據可靠、有序傳輸,傳輸完四次揮手斷開連接。
優點:數據傳輸可靠;
缺點:建立 / 斷開連接有額外耗時,速度稍慢。適合文件傳輸(需完整數據 )、登錄驗證(關鍵數據不能丟 )等場景 。
- 有連接
- 可靠傳輸
- 面向字節流
- 有接收緩沖區,也有發送緩沖區
- 大小不限
????????對于字節流來說,可以簡單的理解為,傳輸數據是基于IO流,流式數據的特征就是在IO流沒有關閉的情況下,是無邊界的數據,可以多次發送,也可以分開多次接收。
2.3?Java數據報套接字通信模型
????????對于UDP協議來說,具有無連接,面向數據報的特征,即每次都是沒有建立連接,并且一次發送全部數據報,一次接收全部的數據報。
????????java中使用UDP協議通信。主要基于 DatagramSocket(收發器 )和 DatagramPacket(數據報載體 )實現。發送時,把數據轉字節數組,構建 DatagramPacket 并指定目標地址 / 端口,通過 DatagramSocket 發送;接收時,創建空 DatagramPacket 當緩沖區,用 DatagramSocket 接收,再解析數據 。對于一次發送及接收UDP數據報的流程如下:
????????以上只是一次發送端的UDP數據報發送,及接收端的數據報接收,并沒有返回的數據。也就是只有請求,沒有響應。對于一個服務端來說,重要的是提供多個客戶端的請求處理及響應,流程如下:?
2.4?Java流套接字通信模型
? ? ? ? java中使用TCP通信協議,主要依賴 ServerSocket(服務端 “大門”,監聽客戶端連接 )和 Socket(客戶端 / 服務端連接后的數據通道 )。服務端用 ServerSocket 綁定端口監聽,如 ServerSocket serverSocket = new ServerSocket(9999); ;客戶端用 Socket 連服務端,如 Socket clientSocket = new Socket("127.0.0.1", 9999); 。連接建立后,通過 Socket 的輸入輸出流(像水管 )收發數據,保證數據有序、可靠傳輸 。
2.5 Socket 編程注意事項:避坑要點?
在 Socket 編程開發中,這些關鍵問題得留意,避免踩坑:
-
客戶端與服務端部署場景
開發調試時,常在同一主機開兩個進程模擬客戶端、服務端,但真實環境里,客戶端和服務端一般分屬不同主機 。開發要考慮跨主機通信的網絡差異(如防火墻、網段限制),別只依賴本地調試邏輯。 -
目標標識:IP + 端口
數據傳輸得明確 “終點”,目的 IP?定位目標主機,目的端口號?定位主機內接收數據的進程。編程時要準確設置這兩個參數,否則數據發錯地方,通信直接失敗。 -
套接字類型與協議層
Socket 編程用?流套接字(基于 TCP)?或?數據報套接字(基于 UDP)?開發,對應傳輸層 TCP/UDP 協議。但光有傳輸層還不夠,應用層協議得自己設計(比如定義數據包格式、指令含義),這部分后續會詳細講怎么規劃。 -
端口占用沖突問題? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 如果一個進程A已經綁定了一個端口,再啟動一個進程B綁定該端口,就會報錯,這種情況也叫端口被占用。對于java進程來說,端口被占用的常見報錯信息如下:
?此時需要檢查進程B綁定的是哪個端口,再查看該端口被哪個進程占用。
解決端口被占用的問題:
- 如果占用端口的進程A不需要運行,就可以關閉A后,再啟動需要綁定該端口的進程B
- 如果需要運行A進程,則可以修改進程B的綁定端口,換為其他沒有使用的端口。
3. UDP 數據報套接字編程:簡單高效的 “快傳” 實踐?
3.1?API 介紹:核心工具
3.1.1 DatagramSocket
????????UDP 通信的 “收發器”,用于發送和接收UDP數據報。服務端常綁定固定端口(如 DatagramSocket serverSocket = new DatagramSocket(8888); ),方便客戶端定位;客戶端一般不綁定固定端口(DatagramSocket clientSocket = new DatagramSocket(); ),由系統分配 。
構造方法:
常用方法:?
3.1.2 DatagramPacket?
????????“數據報載體”,包含數據、目標地址(發送時 )或源地址(接收時 )。發送時,構建 DatagramPacket 需指定數據字節數組、目標地址 / 端口;接收時,創建空 DatagramPacket 當緩沖區,用 DatagramSocket 接收后解析數據 。
構造方法:
常用方法:?
????????構造UDP發送的數據報時,需要傳入 SocketAddress ,該對象可以使用 InetSocketAddress
來創建。?
3.1.3?InetSocketAddress
????????InetSocketAddress 是 SocketAddress 的子類。
構造方法:
3.2 代碼示例:UDP 通信全流程?
3.2.1 UDP Echo Server
public class UdpEchoServer {// 用于網絡通信的套接字對象,就像服務器的"通信接口"private DatagramSocket socket = null;// 服務器構造方法,需要指定一個端口號來啟動服務// 端口號就像快遞柜的編號,方便客戶端找到對應的服務public UdpEchoServer(int port) throws SocketException {// 綁定指定端口,啟動服務器的通信接口socket = new DatagramSocket(port);}// 服務器的啟動方法,一旦調用就開始工作public void start() throws IOException {System.out.println("服務器啟動成功!正在等待客戶端連接...");// 服務器需要一直運行,用死循環來保持工作狀態while (true) {// 每次循環處理一個客戶端的請求和響應// 1. 創建一個數據包對象,用來接收客戶端發來的數據// 就像準備一個"收件盒",指定最大能裝4096字節的數據DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);// 等待接收客戶端的數據,這一步會"卡住"直到有數據到來socket.receive(requestPacket);// 把接收到的字節數據轉換成字符串,方便處理// 注意:只轉換實際收到的長度,避免多余的空字符String request = new String(requestPacket.getData(), 0, requestPacket.getLength());// 2. 處理請求,得到響應結果// 對于回顯服務器來說,直接把收到的內容返回即可String response = process(request);// 3. 把響應結果發回給客戶端// 準備一個"發件盒",裝著要發送的內容,以及客戶端的地址和端口DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), // 要發送的內容(轉換為字節數組)response.getBytes().length, // 內容的長度requestPacket.getSocketAddress() // 客戶端的地址和端口(從請求中獲取));// 發送響應數據socket.send(responsePacket);// 打印日志,記錄通信詳情System.out.printf("[客戶端 %s:%d] 收到: %s, 回復: %s\n",requestPacket.getAddress().toString(), // 客戶端IP地址requestPacket.getPort(), // 客戶端端口request, // 客戶端發送的內容response // 服務器回復的內容);}}// 處理請求的方法// 這里實現的是回顯功能:輸入什么,就返回什么public String process(String request) {return request;}// 程序入口:啟動服務器public static void main(String[] args) throws IOException {// 創建服務器實例,指定端口號9090UdpEchoServer server = new UdpEchoServer(9090);// 啟動服務器server.start();}
}
3.2.2?UDP Echo Client
public class UdpEchoClient {// 客戶端的通信接口,就像打電話用的"手機"private DatagramSocket socket = null;// 服務器的IP地址,類似對方的"電話號碼"private String serverIp;// 服務器的端口號,類似對方"手機上的某個APP"private int serverPort;// 客戶端構造方法:需要知道服務器的IP和端口才能連接// IP地址格式是"點分十進制",比如"192.168.1.1"public UdpEchoClient(String serverIp, int serverPort) throws SocketException {this.serverIp = serverIp;this.serverPort = serverPort;// 初始化客戶端的通信接口(不用指定端口,系統會自動分配一個)socket = new DatagramSocket();}// 客戶端啟動方法:開始和服務器通信public void start() throws IOException {System.out.println("客戶端啟動成功!可以開始發送消息了...");// 創建Scanner對象,用于讀取用戶在控制臺輸入的內容Scanner scanner = new Scanner(System.in);// 循環處理:不斷讀取用戶輸入并和服務器交互while (true) {// 顯示提示符號,告訴用戶可以輸入內容了System.out.print("-> ");// 檢查用戶是否還有輸入(如果輸入結束就退出循環)if (!scanner.hasNext()) {break;}// 讀取用戶輸入的內容(這就是要發給服務器的請求)String request = scanner.next();// 構造要發送的數據包:相當于把消息裝進"信封"DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), // 要發送的內容(轉成字節數組)request.getBytes().length, // 內容的長度InetAddress.getByName(serverIp), // 服務器的IP地址(把字符串轉成網絡地址)serverPort // 服務器的端口號);// 發送數據包:相當于把信封"寄出去"socket.send(requestPacket);// 準備接收服務器的回復:創建一個"收件盒"DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);// 等待接收服務器的回復:這一步會"卡住"直到收到消息socket.receive(responsePacket);// 把服務器回復的字節數據轉成字符串String response = new String(responsePacket.getData(), 0, responsePacket.getLength());// 在控制臺顯示服務器的回復內容System.out.println(response);}}// 程序入口:啟動客戶端public static void main(String[] args) throws IOException {// 創建客戶端實例,連接到本機(127.0.0.1)的9090端口服務器UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);// 啟動客戶端client.start();}
}
3.2.3?3. UDP Dict Serve 字典服務器
思路:客戶端發單詞,服務器查字典(用 Map 存單詞 - 翻譯 )返回翻譯。只需要重寫 process。比如初始化 Map<String, String> dict = new HashMap<>(); ,存入 dict.put("apple", "蘋果"); 等。服務器接收單詞后,在 dict 中查找,把翻譯當響應發回;客戶端發請求、收翻譯并展示。通過這個示例,能更靈活理解 UDP 套接字的應用 。
4.?TCP 流套接字編程:可靠傳輸的 “保障”
4.1?API 介紹:構建可靠連接
4.1.1?ServerSocket
????????ServerSocket 是創建TCP服務端Socket的API。是服務端 “大門”,監聽客戶端連接。創建時綁定端口,如ServerSocket serverSocket = new ServerSocket(9090);然后通過 serverSocket.accept() 阻塞等客戶端連接,有連接時返回 Socket 用于通信 。
構造方法:
其它方法:?
4.1.2?Socket
????????Socket 是客戶端發起連接或服務端通過 ServerSocket.accept () 響應連接后得到的通信端點,雙方建立連接后,用其保存的對端信息收發數據,客戶端創建時需指定服務端 IP 和端口 ,服務端通過該 Socket 的輸入輸出流交互 。
構造方法:
其它方法:
4.2?代碼示例:TCP 通信實踐
4.2.1 TCP Echo Server
public class TcpEchoServer {// 服務器的"總機",負責接聽客戶端的連接請求ServerSocket serverSocket = null;// 構造方法:創建服務器并指定端口號(就像給總機分配一個電話號碼)public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}// 啟動服務器的方法public void start() throws IOException {System.out.println("服務器啟動成功!等待客戶端連接...");// 創建一個線程池,用來處理多個客戶端的請求(相當于多個接線員)// newCachedThreadPool表示可以根據需要自動創建新線程ExecutorService pool = Executors.newCachedThreadPool();// 服務器一直運行,不斷接收新的客戶端連接while (true) {// 等待客戶端連接(總機接聽電話)// 這一步會"卡住",直到有客戶端來連接Socket clientSocket = serverSocket.accept();// 收到連接后,交給線程池處理(安排一個接線員專門服務這個客戶端)pool.submit(new Runnable() {@Overridepublic void run() {processConnection(clientSocket);}});}}// 處理單個客戶端連接的方法(接線員和客戶的通話過程)private void processConnection(Socket clientSocket) {// 打印客戶端上線信息:包含客戶端的IP和端口System.out.printf("[%s:%d] 客戶端上線啦\n", clientSocket.getInetAddress(), clientSocket.getPort());// try-with-resources語法:自動關閉資源,不用手動寫close()try (// 獲取輸入流:用來讀取客戶端發送的消息(相當于聽客戶說話)InputStream inputStream = clientSocket.getInputStream();// 獲取輸出流:用來向客戶端發送消息(相當于跟客戶說話)OutputStream outputStream = clientSocket.getOutputStream()) {// 創建Scanner對象,方便讀取輸入流中的文本Scanner scanner = new Scanner(inputStream);// 循環處理客戶端的請求while (true) {// 判斷客戶端是否還有數據發送(如果沒有,說明客戶端要下線了)if (!scanner.hasNext()) {System.out.printf("[%s:%d] 客戶端下線啦\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}// 1. 讀取客戶端發送的請求內容String request = scanner.next();// 2. 處理請求(回顯服務器直接返回相同內容)String response = process(request);// 3. 把響應寫回給客戶端// 創建PrintWriter方便寫入文本PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);// 刷新緩沖區:確保數據立刻發送出去(不然可能留在緩存里)printWriter.flush();// 打印日志:記錄這次通信的詳情System.out.printf("[%s:%d] 收到: %s, 回復: %s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}} catch (IOException e) {// 捕獲并打印異常信息(比如網絡中斷等問題)e.printStackTrace();}}// 處理請求的核心方法(回顯邏輯:輸入什么返回什么)private String process(String request) {return request;}// 程序入口:啟動服務器public static void main(String[] args) throws IOException {// 創建服務器實例,綁定9090端口TcpEchoServer server = new TcpEchoServer(9090);// 啟動服務器server.start();}
}
4.2.2?TCP Echo Client
public class TcpEchoClient {// 客戶端的"電話",用來和服務器建立連接并通話private Socket clientSocket = null;// 構造方法:初始化客戶端,需要知道服務器的IP和端口(相當于知道對方的電話號碼)public TcpEchoClient(String serverIp, int serverPort) throws IOException {// 連接服務器:就像撥打指定的電話號碼clientSocket = new Socket(serverIp, serverPort);}// 啟動客戶端:開始和服務器通信public void start() {System.out.println("客戶端啟動成功!可以開始聊天啦...");// try-with-resources語法:自動關閉輸入輸出流,不用手動關閉try (// 輸入流:用來接收服務器發過來的消息(相當于耳朵,聽服務器說話)InputStream inputStream = clientSocket.getInputStream();// 輸出流:用來向服務器發送消息(相當于嘴巴,跟服務器說話)OutputStream outputStream = clientSocket.getOutputStream()) {// 掃描器1:用來讀取用戶在控制臺輸入的內容(從鍵盤讀)Scanner scannerConsole = new Scanner(System.in);// 掃描器2:用來讀取服務器發送過來的消息(從網絡讀)Scanner scannerNetwork = new Scanner(inputStream);// 打印器:用來向服務器發送消息(包裝輸出流,方便寫文本)PrintWriter writer = new PrintWriter(outputStream);// 循環聊天:不斷讀取用戶輸入并發送給服務器,再接收服務器回復while (true) {// 顯示提示符號,告訴用戶可以輸入內容了System.out.printf("-> ");// 檢查用戶是否還有輸入(如果沒有輸入,就退出循環)if (!scannerConsole.hasNext()) {break;}// 1. 讀取用戶在控制臺輸入的內容(要發給服務器的消息)String request = scannerConsole.next();// 2. 把消息發送給服務器writer.println(request);// 刷新緩沖區:確保消息立刻發出去(不然可能存在緩存里沒發送)writer.flush();// 3. 等待并讀取服務器的回復String response = scannerNetwork.next();// 4. 在控制臺顯示服務器的回復內容System.out.println(response);}} catch (IOException e) {// 捕獲并打印異常(比如網絡斷開等問題)e.printStackTrace();} finally {// 最后一定要關閉客戶端的"電話",釋放資源try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}// 程序入口:啟動客戶端public static void main(String[] args) throws IOException {// 創建客戶端實例,連接到本機(127.0.0.1)的9090端口服務器TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);// 啟動客戶端,開始通信client.start();}
}
5. 長短連接:按需選擇通信模式
????????在 TCP 通信體系里,連接的建立與關閉時機,直接界定了短連接和長連接兩種模式。合理選用,能讓程序在效率、資源占用間找到平衡,適配不同業務場景。
5.1 短連接與長連接的定義
TCP 傳輸數據依賴先建立連接,“何時關閉連接” 是區分短、長連接的核心:
- 短連接:每次完成 “接收數據 + 返回響應” 后,立即關閉連接 。如同 “一錘子買賣”,一次連接僅支撐單次收發數據,下次交互需重新建連。
- 長連接:保持連接狀態不關閉,允許雙方持續收發數據 。像 “持續對話”,一次建連可支撐多次數據交互,直至主動斷開或網絡異常。
5.2 短連接與長連接的核心區別
對比短、長連接,從建連開銷到適用場景,差異顯著:
5.2.1 連接建立與關閉的耗時差異
- 短連接:每次請求 - 響應,都要經歷 “建連→傳數據→關連” 全流程 。頻繁交互時,建連、關連的耗時會疊加,拖慢整體效率。
- 長連接:僅首次需完整建連,后續請求 - 響應直接復用已連通道 。省掉重復建連、關連的耗時,高頻交互場景下效率優勢明顯。
5.2.2 主動請求的發起方差異
- 短連接:通常由客戶端主動發起請求,服務端被動響應 。典型如瀏覽器訪問靜態網頁,客戶端發請求,服務端回數據后關連,服務端很少主動 “推送”。
- 長連接:支持雙向主動通信 。既允許客戶端主動發請求(如聊天時發消息 ),也支持服務端主動推送數據(如即時通訊的新消息通知、實時行情更新 )。
5.2.3 適用場景差異
- 短連接:適配客戶端請求頻率低的場景 。如瀏覽新聞網頁(單次請求即可獲取內容 )、查詢靜態數據接口(查天氣、物流信息 ),無需持續保持連接,“即用即關” 省資源。
- 長連接:專為客戶端與服務端高頻通信設計 。像聊天室(持續收發消息 )、實時游戲(同步玩家操作、狀態 )、金融行情推送(秒級更新數據 ),依賴長連接實現低延遲、高實時性交互。
5.3 長連接的 “擴展痛點” 與優化方向
????????長連接雖高效,但若基于傳統 BIO(同步阻塞 IO )實現,會面臨系統資源占用過高問題:
5.3.1 BIO 長連接的資源瓶頸
????????BIO 模式下,每個長連接對應一個阻塞線程?。連接需持續 “阻塞等待” 數據,若有 1 萬長連接,服務端需創建 1 萬線程,內存、線程切換開銷會直接 “壓垮” 系統。
5.3.2 NIO 優化長連接的思路
????????為解決 BIO 缺陷,Java 引入?NIO(同步非阻塞 IO )?,通過?Selector(多路復用器 )?實現:
- 用少量線程管理大量連接,線程無需阻塞等待,而是由 Selector 監聽 “哪些連接有數據可讀 / 可寫”,按需處理。
- 極大降低資源消耗,支撐高并發長連接場景(如百萬級在線的即時通訊系統 ),主流框架(Netty )也基于 NIO 封裝,簡化長連接開發。
5.4 總結:按需選型,平衡效率與資源
????????短連接 “簡單輕量”,適合低頻交互;長連接 “高效持久”,適配高頻、實時場景 。實際開發中,需結合業務需求:
- 若做新聞網站、靜態數據接口,選短連接省心省力;
- 若開發聊天、實時游戲,長連接是必選項,且建議基于 NIO/Netty 優化資源占用。
理解長短連接的本質差異,才能讓網絡通信既高效又穩定,貼合業務需求 。