BIO NIO AIO
BIO(同步阻塞)
- 傳統的網絡編程模型中採用C/S模型,即客戶端/服務端模型。這時候通常採用的是BIO(Blocking Input Output)即同步阻塞的。
- 優點:實現簡單,對於低併發、低訪問量的需求適合。
- 缺點:其訪問模型是針對一個客戶端,在服務端對應一個線程去處理。在極端情況下,很容易出現線程數量過大(訪問的客戶數量過多,高併發情況)使得系統資源耗盡的情況。即客戶端:服務端線程=1:1
- 優化方案:採用線程池統一管理線程。但仍然不能從根本上改進在高併發情況下線程數目過大導致的系統資源耗盡問題
Server端代碼並啓動
package NetWorkProgramming;
import jdk.internal.util.xml.impl.Input;
import javax.naming.ldap.SortKey;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static int a = 1;
//單例
private ServerSocket serverSocket;
private Socket clientSocket;
//本機迴路
private static final String ipAddress = "127.0.0.1";
//默認監聽9999端口
private static final int port = 9999;
public void startServer() throws IOException {
serverSocket = getServerSocket(port);
System.out.println("服務器套接字成功初始化....." + "端口爲" + port);
//一直監聽端口信號
}
private ServerSocket getServerSocket(int port) throws IOException {
return new ServerSocket(port);
}
public static void main(String[] args) throws IOException, InterruptedException {
//啓動服務端 BIO 同步阻塞模式
Server server = new Server();
server.startServer();
Thread serverThread = new Thread(new Runnable() {
@Override
public void run() {
//讀取套接字傳輸的數據
BufferedReader in;
try {
while (true) {
System.out.println("阻塞中....");
server.clientSocket = server.serverSocket.accept();
in = new BufferedReader(new InputStreamReader(server.clientSocket.getInputStream()));
System.out.println("阻塞結束 ");
String expr;
//套接字中的數據
while (true) {
if ((expr = in.readLine()) == null) break;
System.out.println("套接字中數據是: " + expr);
}
}
} catch (IOException e) {
System.out.println("服務端套接字初始化失敗...." + "端口號是:" + port);
e.printStackTrace();
} finally {
try {
server.serverSocket.close();
server.clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
serverThread.start();
//保證完全初始化 啓動
Thread.sleep(300);
//啓動客戶端線程
Thread clientThread = new Thread(new Runnable() {
@Override
public void run() {
Client client = new Client();
//注意這個地方 加上換行符才能正確結束readLine()函數
client.startClient(port, ipAddress, "這是第一次套接字測試哦!\n");
}
});
clientThread.start();
}
}
Client端代碼
package NetWorkProgramming;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class Client {
private Socket clientSocket;
private void newInstanceClientSocket(int port, String ipAddress) {
try {
clientSocket = new Socket(ipAddress, port);
} catch (IOException e) {
System.out.println("初始化客戶端套接字失敗...." + "Ip爲" + ipAddress);
e.printStackTrace();
}
}
public void startClient(int port, String ipAddress, String info) {
newInstanceClientSocket(port, ipAddress);
try {
OutputStream output = clientSocket.getOutputStream();
//寫入數據
output.write(info.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
問題總結
- BufferReader的readLine()方法是阻塞函數,即如果沒有讀取到末尾,無換行符會一直阻塞,在使用的時候要格外小心。筆者在實現Server端代碼main函數中註釋有提及
- 這裏的代碼其實寫的不嚴謹。正確的應該是在中間過程使用一個新的Handler類作爲代理來處理,這樣才能滿足BIO的線程:客戶端=1:1的情況
NIO(New I/O 或者稱之爲 Non-Block I/O)
- 簡介:出現在JDK4 用於解決BIO模式在高併發情況下性能不佳、可用性差的問題。採用多路複用技術。
- 主要相關類及接口:Buffer(緩衝區,相當於BIO中流中的數據)、Channel(通道,相當於BIO的流,但是通道是全雙工的,更接近操作系統底層)、Selector(多路複用器,通過令牌輪詢的方式訪問已經就緒的通道,達到多路複用異步的效果)
- 優點:非阻塞,提高了在高併發情況下的可用性和效率
- 缺點:實現複雜
AIO(Asynchornous I/O 或者稱之爲 NIO 2.0)
- 簡介:JDK7 纔出現的特性,利用了Linux epoll方法,實現了真正的異步非阻塞。
- 主要相關類及接口:AsynchronousServerSocketChannel(異步服務端套接字通道) CompletionHandler,Future(分別對應accept的兩種返回。因爲AIO是真正異步的,所以不會阻塞客戶端。通常使用第一種,自己實現通道的回調函數,在調用成功/失敗時額分別觸發相應的completion和failed方法)
- 優點:基於Linux 2.6內核,在操作系統上實現了真·異步操作,性能好,可用性強
缺點:實現較爲複雜
套接字編程 Select Poll Epoll
Select
- 簡介:select是最早出現套接字I/O模型。在Linux下,系統認爲所有的設備都是文件,並對每個設備都使用文件描述符(FD)進行描述。而在Linux內核中,將select和poll模式下的FD大小都硬編碼爲1024。Select通過遍歷fd_set集合取得需要讀寫的操作。
- 優點:實現簡單
缺點:(1)由於FD的最大數1024的限制,限制了服務端接入設備的最大限制,不適應現在的高併發情況。(2)採用了遍歷FD集合的方式,效率低下。(3)對進程通信採用了內存拷貝的方式,效率低
Poll
簡介:相對於select進行改進了的一種模式,對文件集合採用的pollfd而非前面的fd_set,本質上並無太大改變。
- 優點:同上
- 缺點:同上
Epoll
- 簡介:目前高併發情況下最常用的I/O多路複用模型。有三個函數epoll_create、epoll_ctl、epoll_wait,前面兩個select/poll模型都只有一個函數。