Java目前有三種IO相關的API了,下面簡單的說一下:
BIO,阻塞IO,最常用的Java IO API,提供一般的流的讀寫功能。相信學習Java的人,都用過。
NIO,非阻塞IO,在JDK1.4中開始出現,大量應用與伺服器端程式設計,用于提高并發通路的性能,常用的NIO架構有Netty,Mina。
AIO,異步IO,在JDK1.7開始出現。還沒有了解過,等以後了解了再說。
阻塞、非阻塞,同步、異步
在寫這篇文章前,在網上了解了一下,其中争議最的問題要數阻塞、非阻塞怎麼了解,異步、同步怎麼了解。
由于每個人想法的不同,很難達到一個一緻的答案,又沒有真正的大牛出來給這一個準确的定義。這裡也簡單的說一下,我對這兩組名詞的了解。
1)阻塞、非阻塞
我認為,BIO,NIO沒有大家想的那麼複雜,就是底層實作中進行資料的讀寫(IO)采取的兩種方案,隻不過非阻塞讀寫要比阻塞IO讀寫更快一些。
bio中的InputStream#read()是一個block方法。
2)同步、異步
同步與異步,我認為說的并不是IO本身,我認為說的是程式采用的程式設計模型,也就是說采用的是同步的程式設計模型還是異步的程式設計模型。
BIO、NIO,他們的差別是作業系統讀寫資料采用的方式,他們是Java中的概念,在Java領域,他們的底層實作采用的是同步的程式設計模型。是以說BIO、NIO都是同步的。
AIO的底層實作應當是異步的程式設計模型,是以說它是異步IO。
這裡我隻是闡述了我對它們的了解,沒有與大家争論到底怎麼去了解他們。也許我沒有大家想的那麼深遠,畢竟我隻是學習了NIO不到一天時間而已。
針對BIO、NIO,伺服器程式設計如何提高性能
一個程式運作的快慢,一般有會受到兩個因素的影響:1)程式代碼是否高效,2)IO讀寫是否高效。曾經看過這麼一幅圖,大緻内容是:一幫不同角色的人(程式員、運維、項目經理等角色的人)在一起讨論一個應用程式效率地下的問題。
程式員說的是:給我3個月時間,我能夠讓程式運作效率提高,當然了,我要調整代碼的整體結構…
運維說:…
項目經理說:換用讀寫更快的硬體裝置解決這個問題。
故事我已經無法還原,但是這個故事說的内容就是程式優化帶來的效率的提升遠不及提高IO速度帶來的提升。
相比于BIO,NIO就是從讀寫來提升效率的。性能對于伺服器來說尤為重要,伺服器端程式設計并不是都采用了NIO程式設計。
Tomcat伺服器内部,就有BIO、NIO兩種方式。
1)BIO如何提高并發通路
BIO,是一種阻塞IO,伺服器端使用BIO進行資料讀寫時,一般都是采用了一個Socket請求對應一個Thread的方式來提高性能的。
但是一台伺服器上,可以跑的線程數量也是有限制的:線程不是越多越好,畢竟線程間的切換,也是有不小的開銷。也不是越少越好,線程太少,極端情況下一個線程,如果用一個線程來解決使用者的并發通路,伺服器接收一個客戶的請求時,其他人都要處于等待狀态。你通路網頁,多數情況下超過5秒,估計你就關掉它了吧。
或者采用線程池方案。
2)采用NIO程式設計時 如何提高并發通路
采用選擇器輪詢可用通道,讀寫資料。具體的怎麼做的就不說了,網上一大坨一大坨的,雖然網上大家寫的大多是copy别人的。下面給會出一個例子,是以這裡就不多說了,不知道的可以網上找相關的文章。
一個Thread下開一個Selector,一個Selector處理多個Socket通道(也就是多個用于請求),這樣就是一個Thread線程可以同時處理多個使用者請求。
孰優孰劣
假若說,伺服器設定同時處理1000個使用者請求(也就是1000個處理使用者請求的線程)。假若有10000個人來發請求。
如果采用BIO API程式設計,那麼就同時隻能為1000個人服務,其他的9000人就處于等待狀态。
如果采用NIO API程式設計,也開啟1000個線程,因為一個Thread可以同時處理多個使用者請求,咱不說讓它處理太多了,就處理10個吧,這樣算下來,這個10000個使用者請求,就都可以處理了。
BIO(用戶端)與NIO(服務端)通信
今天學習了NIO,就用NIO來處理浏覽器使用者請求吧。浏覽器發送的肯定不是采用NIO API發送Socket請求的,肯定是使用了阻塞式IO,也就是對應于Java中的BIO了。
package com.fjn.other.nio.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@SuppressWarnings({ "unchecked" })
public class NioServer {
ServerSocketChannel serverChannel;
ServerSocket serverSocket;
public final int port;
private Selector selector;
ByteBuffer buffer = ByteBuffer.allocate(1024);
NioServer(final int port) {
this.port = port;
}
void init() throws Exception {
// 建立 ServerSocketChannel、ServerSocket
serverChannel = ServerSocketChannel.open();
serverSocket = serverChannel.socket();
serverSocket.bind(new InetSocketAddress(port));
// 設定通道為非阻塞模式
serverChannel.configureBlocking(false);
// 開啟通道選擇器,并注冊 ServerSocketChannel
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
void go() throws Exception {
while (true) {
int num = selector.select();
if (num <= 0)
continue;
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
while (keyIter.hasNext()) {
final SelectionKey key = keyIter.next();
// 接收一個Socket連接配接
// key.isAcceptable()如果為true,說明channnel支援accept(),也就是說明是一個ServerSocketChannel
if (key.isAcceptable()) {
SocketChannel clientChannel = serverChannel.accept();
if (clientChannel != null) {
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ
| SelectionKey.OP_WRITE);
}
}
// 如果isReadable()為true,說明是一個SocketChannel
if (key.isReadable()) {
String requestContent = read(key);
// 業務處理
// responseContent=doSomthing(requestContent);
write(key, "ok" /* responseContent */);
}
keyIter.remove();
}
}
}
// 從通道讀取資料
String read(SelectionKey key) throws Exception {
SocketChannel socketChannel = (SocketChannel) key.channel();
buffer.clear();// 這一步必須有
int len = 0;
StringBuffer str=new StringBuffer();
while ((len = socketChannel.read(buffer)) > 0) {
byte[] bs = buffer.array();
String block=new String(bs, 0, len);
System.out.println("Server read: " + block);
str.append(block);
}
buffer.clear();
return str.toString();
}
// 寫資料到通道
void write(SelectionKey key, String str) throws Exception {
SocketChannel socketChannel = (SocketChannel) key.channel();
buffer.clear();
buffer.put(str.getBytes());
buffer.flip();// 這一步必須有
socketChannel.write(buffer);
}
public static void main(String[] args) throws Exception {
final int port = 10000;
NioServer server = new NioServer(port);
server.init();
///========================================================
// 接下來模拟3個Client并發通路伺服器
int poolsize = 3;
ExecutorService pool = Executors.newFixedThreadPool(poolsize);
Collection<Callable> tasks = new ArrayList<Callable>(10);
final String clientname="clientThread";
for (int i = 0; i < poolsize; i++) {
final int n = i;
// 若每一個Client都保持使用BIO方式發送資料到Server,并讀取資料。
tasks.add(new Callable() {
@Override
public Object call() throws Exception {
Socket socket = new Socket("127.0.0.1", port);
final InputStream input = socket.getInputStream();
final OutputStream out = socket.getOutputStream();
final String clientname_n = clientname + "_" + n;
// BIO讀取資料線程
new Thread(clientname_n + "_read") {
@Override
public void run() {
byte[] bs = new byte[1024];
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int len = 0;
try {
while ((len = input.read(bs)) != -1) {
System.out.println("Clinet thread "
+ Thread.currentThread()
.getName() + " read: "
+ new String(bs, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
// BIO寫資料線程
new Thread(clientname_n + "_write") {
@Override
public void run() {
int a = 0;
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String str = Thread.currentThread().getName()
+ " hello, " + a;
try {
out.write(str.getBytes());
a++;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
return null;
}
});
}
pool.invokeAll((Collection<? extends Callable<Object>>) tasks);
server.go();
}
}
View Code
上面的測試的是3個Client采用BIO API不斷的并發的發送Socket 請求到Server端。Server采用NIO API處理Client的請求并作出響應,然後Client接收響應。
作者: 房繼諾
出處:http://www.cnblogs.com/f1194361820
版權:本文版權歸作者和部落格園共有
歡迎轉載,轉載請需要注明部落格出處
技術交流QQ:1194361820,加好友請注明:來自部落格園,不要說你是部落格園,也可以掃描圖像二維碼直接加我。