Socket是什么呢?
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。
在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標准的協議集,它是為廣域網(WANs)設計的。
UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。
Socket 原理
Socket 連接,至少需要一對套接字,分為 clientSocket,serverSocket 連接分為3個步驟:
(1) 服務器監聽:服務器並不定位具體客戶端的套接字,而是時刻處於監聽狀態;
(2) 客戶端請求:客戶端的套接字要描述它要連接的服務器的套接字,提供地址和端口號,然后向服務器套接字提出連接請求;
(3) 連接確認:當服務器套接字收到客戶端套接字發來的請求后,就響應客戶端套接字的請求,並建立一個新的線程,把服務器端的套接字的描述發給客戶端。一旦客戶端確認了此描述,就正式建立連接。而服務器套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求.
過程圖解:
先從服務器端說起。服務器端先初始化Socket,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。
在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。
客戶端發送數據請求,服務器端接收請求並處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束。
實例展示:
服務器端:
package com.socket.test; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class SocketServer { public static void main(String[] args) throws Exception { // 監聽指定的端口 int port = 55533; ServerSocket server = new ServerSocket(port); // server將一直等待連接的到來 System.out.println("server將一直等待連接的到來"); Socket socket = server.accept(); // 建立好連接后,從socket中獲取輸入流,並建立緩沖區進行讀取 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; StringBuilder sb = new StringBuilder(); //只有當客戶端關閉它的輸出流的時候,服務端才能取得結尾的-1 while ((len = inputStream.read(bytes)) != -1) { // 注意指定編碼格式,發送方和接收方一定要統一,建議使用UTF-8 sb.append(new String(bytes, 0, len, "UTF-8")); } System.out.println("get message from client: " + sb); // 發送信息給客戶端 OutputStream outputStream = socket.getOutputStream(); outputStream.write("Hello Client,I get the message.".getBytes("UTF-8")); inputStream.close(); outputStream.close(); socket.close(); server.close(); } }
當讀取完客戶端的消息后,打開輸出流,將指定消息發送回客戶端
客戶端:
package com.socket.test import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class SocketClient { public static void main(String args[]) throws Exception { // 要連接的服務端IP地址和端口 String host = "127.0.0.1"; int port = 55533; // 與服務端建立連接 Socket socket = new Socket(host, port); // 建立連接后獲得輸出流 OutputStream outputStream = socket.getOutputStream(); String message = "你好 fuwuqiduan"; socket.getOutputStream().write(message.getBytes("UTF-8")); //通過shutdownOutput高速服務器已經發送完數據,后續只能接受數據 socket.shutdownOutput(); InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; StringBuilder sb = new StringBuilder(); while ((len = inputStream.read(bytes)) != -1) { //注意指定編碼格式,發送方和接收方一定要統一,建議使用UTF-8 sb.append(new String(bytes, 0, len,"UTF-8")); } System.out.println("get message from server: " + sb); inputStream.close(); outputStream.close(); socket.close(); } }
客戶端也有相應的變化,在發送完消息時,調用關閉輸出流方法,然后打開輸出流,等候服務端的消息。
服務器段優化
在上面的例子中,服務端僅僅只是接受了一個Socket請求,並處理了它,然后就結束了,但是在實際開發中,一個Socket服務往往需要服務大量的Socket請求,那么就不能再服務完一個Socket的時候就關閉了,這時候可以采用循環接受請求並處理的邏輯:
package com.socket.test import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class SocketServer { public static void main(String args[]) throws IOException { // 監聽指定的端口 int port = 55533; ServerSocket server = new ServerSocket(port); // server將一直等待連接的到來 System.out.println("server將一直等待連接的到來"); while(true){ Socket socket = server.accept(); // 建立好連接后,從socket中獲取輸入流,並建立緩沖區進行讀取 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; StringBuilder sb = new StringBuilder(); while ((len = inputStream.read(bytes)) != -1) { // 注意指定編碼格式,發送方和接收方一定要統一,建議使用UTF-8 sb.append(new String(bytes, 0, len, "UTF-8")); } System.out.println("get message from client: " + sb); inputStream.close(); socket.close(); } } }
這種一般也是新手寫法,但是能夠循環處理多個Socket請求,不過當一個請求的處理比較耗時的時候,后面的請求將被阻塞,所以一般都是用多線程的方式來處理Socket,即每有一個Socket請求的時候,就創建一個線程來處理它。
不過在實際生產中,創建的線程會交給線程池來處理,為了:
- 線程復用,創建線程耗時,回收線程慢
- 防止短時間內高並發,指定線程池大小,超過數量將等待,方式短時間創建大量線程導致資源耗盡,服務掛掉
package com.socket.test import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SocketServer { public static void main(String args[]) throws Exception { // 監聽指定的端口 int port = 55533; ServerSocket server = new ServerSocket(port); // server將一直等待連接的到來 System.out.println("server將一直等待連接的到來"); //如果使用多線程,那就需要線程池,防止並發過高時創建過多線程耗盡資源 ExecutorService threadPool = Executors.newFixedThreadPool(100); while (true) { Socket socket = server.accept(); Runnable runnable=()->{ try { // 建立好連接后,從socket中獲取輸入流,並建立緩沖區進行讀取 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; StringBuilder sb = new StringBuilder(); while ((len = inputStream.read(bytes)) != -1) { // 注意指定編碼格式,發送方和接收方一定要統一,建議使用UTF-8 sb.append(new String(bytes, 0, len, "UTF-8")); } System.out.println("get message from client: " + sb); inputStream.close(); socket.close(); } catch (Exception e) { e.printStackTrace(); } }; threadPool.submit(runnable); } } }