說明
系列文章:http://www.reibang.com/p/594441fb9c9e
本文完全參考自《Netty權(quán)威指南(第2版)》,李林峰著叹螟。
傳統(tǒng) BIO 編程是什么樣的魄宏?
基本的網(wǎng)絡(luò)編程模型是Client/Server
秸侣,即兩個進程間相互通信,其中服務(wù)端提供位置信息(IP地址和端口號)宠互,客戶端通過連接向服務(wù)器監(jiān)聽的地址發(fā)起連接請求味榛,通過三次握手建立連接,之后雙方就可以通過網(wǎng)絡(luò)套接字(socket)進行通信予跌。
Java的傳統(tǒng)同步阻塞模型中搏色,ServerSocket負責(zé)綁定IP地址,啟動監(jiān)聽端口券册;Socket負責(zé)發(fā)起連接频轿。之后雙方通過輸入和輸出流進行同步阻塞式通信垂涯。
BIO 通信模型
對于每個客戶端,服務(wù)端都要新建一個線程航邢。
當(dāng)客戶端并發(fā)訪問量增加后耕赘,服務(wù)端的線程個數(shù)和客戶端并發(fā)訪問數(shù)量呈1:1的關(guān)系,當(dāng)線程數(shù)膨脹后膳殷,系統(tǒng)的性能將急劇下降操骡。
示例
服務(wù)端在接收到字符串QUERY TIME ORDER
后,返回當(dāng)前日期給客戶端赚窃。
TimeServer 源碼
public class TimeServer {
public static void main(String[] args) throws IOException {
int port = 8080;
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("The time server is start in port : " + port);
Socket socket = null;
while (true) {
socket = serverSocket.accept();
new Thread(new TimeServerHandler(socket)).start();
}
} finally {
if (serverSocket != null) {
System.out.println("The time server close");
serverSocket.close();
serverSocket = null;
}
}
}
}
public class TimeServerHandler implements Runnable {
private Socket socket;
public TimeServerHandler(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 currentTime = null;
String body = null;
while (true) {
body = in.readLine();
if (body == null) {
break;
}
System.out.println("The time server receive order : " + body);
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date().toString() : "BAD ORDER";
out.println(currentTime);
}
} catch (Exception e) {
if (in != null) {
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (out != null) {
out.close();
out = null;
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
this.socket = null;
}
}
}
}
TimeClient 源碼
public class TimeClient {
public static void main(String[] args) throws Exception {
int port = 8080;
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket("127.0.0.1", port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("QUERY TIME ORDER");
System.out.println("SEND TO SERVER SUCCESSED");
String resp = in.readLine();
System.out.println("Now is : " + resp);
} catch (Exception e) {
// TODO: handle exception
} finally {
if (out != null) {
out.close();
out = null;
}
if (in != null) {
in.close();
in = null;
}
if (socket != null) {
socket.close();
socket = null;
}
}
}
}
輸出結(jié)果
The time server is start in port : 8080
The time server receive order : QUERY TIME ORDER
The time server receive order : QUERY TIME ORDER
線程堆棧查看
通過jstack
命令册招,查看當(dāng)前堆棧信息:
"main" #1 prio=5 os_prio=31 tid=0x00007ffed1805000 nid=0x1c03 runnable [0x0000700007de7000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at bio.TimeServer.main(TimeServer.java:18)
我們可以看到,代碼確實阻塞在accept
操作上勒极。
偽異步 I/O 編程
同步阻塞I/O當(dāng)海量并發(fā)接入時是掰,會導(dǎo)致線程耗盡『又剩可以對線程模型進行優(yōu)化:后端通過一個線程池來處理多個客戶端的請求接入。
但是偽異步I/O也存在很多問題震叙,因為它的本質(zhì)仍然是同步阻塞掀鹅。這意味著當(dāng)對方發(fā)送請求或者應(yīng)答消息比較緩慢,或者網(wǎng)絡(luò)傳輸較慢時媒楼,讀取輸入流一方的通信線程會被長時間阻塞乐尊;在此期間,其他接入消息只能在消息隊列中排隊划址。
NIO 編程
首先問自己一個問題扔嵌,什么才是NIO編程?對于NIO夺颤,官方的說法是:New I/O痢缎;但是更多的人喜歡稱之為Non-block I/O(非阻塞I/O)。
BIO中的Socket類和ServerSocket類世澜,對應(yīng)于NIO中的SocketChannel和ServerSocketChannel兩種不同的套接字通道實現(xiàn)独旷,這兩種套接字都支持阻塞和非阻塞兩種模式。
- 阻塞模式:使用非常簡單寥裂,但是性能和可靠性都不好嵌洼;
- 非阻塞模式:使用復(fù)雜,性能和可靠性好封恰。
NIO 類庫簡介
緩沖區(qū) Buffer
在面向流的I/O中麻养,可以直接讀取或?qū)懭霐?shù)據(jù)至Stream對象中;在NIO中诺舔,所有數(shù)據(jù)都是通過緩沖區(qū)處理的:讀取數(shù)據(jù)時鳖昌,直接讀到緩沖區(qū)备畦;寫入數(shù)據(jù)時,寫入到緩沖區(qū)遗遵。任何時候訪問NIO中的數(shù)據(jù)萍恕,都是通過緩沖區(qū)進行的。
通道 Channel
傳統(tǒng)的流
只有一個方向(InputStream或者OutputStream)车要,而通道可以用于讀允粤、寫或二者同時進行。
多路復(fù)用器 Selector
提供選擇已經(jīng)就緒的任務(wù)的能力
翼岁。Selector會不斷輪詢注冊在其上的Channel类垫,如果某個Channel上面發(fā)生讀寫事件,這個Channel處于就緒狀態(tài)琅坡,會被Selector輪詢處理悉患,然后通過SelectionKey可以獲取就緒Channel的集合,進行后續(xù)的I/O操作榆俺。
對于NIO的編程十分繁瑣售躁,就不作介紹,在接下來的文章中直接使用Netty開發(fā)茴晋。