一.傳統的BIO實現
Socket又稱“套接字”,應用程序通常通過“套接字”向網絡發出請求或者應答網絡請求。Socket和ServerSocket類庫位於java.net包中,ServerSocket用於服務器,Socket是建立網絡連接時使用的,在連接成功時,應用程序兩端都會產生一個Socket實例,操作這個實例,完成所需的會話。對於一個網絡連接來說,套接字是平等的,不因爲在服務器端或在客戶端而產生不同級別。不管是Socket還是ServerSocket它們的工作都是通過SocketImpl類及其子類完成的。
套接字之間的連接過程可以分爲四個步驟:服務器監聽,客戶端請求服務器,服務器確認,客戶端確認,進行通信。
- 服務器監聽:服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態。
- 客戶端請求:是指由客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。爲此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然後就向服務器端套接字提出連接請求。
- 服務器端連接確認:是指當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求。它就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發給客戶端。
- 客戶端連接確認:一旦客戶端確認了此描述,連接就建立好了,雙方開始進行通信,而服務器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。
下面的實例代碼實現了傳統的Socket套接字編程:
定義服務端類Server.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
final static int PROT = 8765;
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(PROT);
System.out.println(" server start .. ");
//進行阻塞
Socket socket = server.accept();
//新建一個線程執行客戶端的任務
new Thread(new ServerHandler(socket)).start();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (server != null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
server = null;
}
}
}
多線程類ServerHandler.java的定義如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
body = in.readLine();
if (body == null) break;
System.out.println("Server :" + body);
out.println("服務器端回送響的應數據.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
socket = null;
}
}
}
客戶端連接請求類Client.java的定義如下
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Client {
final static String ADDRESS = "127.0.0.1";
final static int PORT = 8765;
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(ADDRESS, PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
//向服務器端發送數據
out.println("接收到客戶端的請求數據...");
String response = in.readLine();
System.out.println("Client: " + response);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
socket = null;
}
}
}
實現瓶頸
Socket套接字網絡編程的基本模型是Client/Server模型,也就是兩個進程直接進行相互通信,其中服務端提供配置信息(綁定IP地址和監聽端口),客戶端通過連接操作向服務端監聽的地址發起連接請求,通過三次握手建立連接,如果連接成功,則雙方即可以進行通信。實例圖如下:
這種傳統的Socket實現方式,每次客戶端連接,服務端都要開啓一個新的線程,當連接的客戶端線程達到一定的數量的時候,服務器端就會達到瓶頸。此時我們可以在服務端用線程池的實現方式,解決服務端的瓶頸。
二.線程池(僞異步)實現
採用線程池和任務隊列可以實現一種僞異步的IO通信框架。我們將客戶端的Socket封裝成一個task任務(實現Runnable接口的類)然後投遞到線程池中去。
Client.java和ServerHandler.java的實現代碼和BIO的實現是一致的,這裏只需要改造一下Server.java,實現代碼如下:
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
final static int PORT = 8765;
public static void main(String[] args) {
ServerSocket server = null;
BufferedReader in = null;
PrintWriter out = null;
try {
server = new ServerSocket(PORT);
System.out.println("server start");
Socket socket = null;
HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
while (true) {
socket = server.accept();
executorPool.execute(new ServerHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if (server != null) {
try {
server.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
server = null;
}
}
}
HandlerExecutorPool的實現代碼如下:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class HandlerExecutorPool {
private ExecutorService executor;
public HandlerExecutorPool(int maxPoolSize, int queueSize) {
this.executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
maxPoolSize,
120L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable task) {
this.executor.execute(task);
}
}