1 IO,NIO,AIO
1.1 各個基本概念
Java I/O
的相關方法如下所述:
- 同步並阻塞 (
I/O
方法):
服務器實現模式為一個連接啟動一個線程,每個線程親自處理I/O
並且一直等待I/O
直到完成,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理。但是如果這個連接不做任何事情就會造成不必要的線程開銷,當然可以通過線程池機制改善這個缺點。
I/O
的局限是它是面向流的、阻塞式的、串行的
一個過程。對每一個客戶端的Socket
連接I/O
都需要一個線程來處理,而且在此期間,這個線程一直被占用,直到Socket
關閉。在這期間,TCP
的連接、數據的讀取、數據的返回都是被阻塞的。也就是說這期間大量浪費了CPU
的時間片和線程占用的內存資源。此外,每建立一個Socket
連接時,同時創建一個新線程對該Socket
進行單獨通信 (采用阻塞的方式通信)。這種方式具有很快的響應速度,並且控制起來也很簡單。在連接數較少的時候非常有效,但是如果對每一個連接都產生一個線程無疑是對系統資源的一種浪費,如果連接數較多將會出現資源不足的情況; - 同步非阻塞 (
NIO
方法):
服務器實現模式為一個請求啟動一個線程,每個線程親自處理I/O
,但是另外的線程輪詢檢查是否I/O
准備完畢,不必等待I/O
完成,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O
請求時才啟動一個線程進行處理。NIO
則是面向緩沖區,非阻塞式的,基於選擇器的
,用一個線程來輪詢監控多個數據傳輸通道,哪個通道准備好了 (即有一組可以處理的數據) 就處理哪個通道。服務器端保存一個Socket
連接列表,然后對這個列表進行輪詢,如果發現某個Socket
端口上有數據可讀時,則調用該Socket
連接的相應讀操作;如果發現某個Socket
端口上有數據可寫時,則調用該Socket
連接的相應寫操作;如果某個端口的Socket
連接已經中斷,則調用相應的析構方法
關閉該端口。這樣能充分利用服務器資源,效率得到大幅度提高; - 異步非阻塞 (
AIO
方法,JDK7 發布):
服務器實現模式為一個有效請求啟動一個線程,客戶端的I/O
請求都是由操作系統先完成了再通知服務器應用去啟動線程進行處理,每個線程不必親自處理I/O
,而是委派操作系統來處理,並且也不需要等待I/O
完成,如果完成了操作系統會另行通知的。該模式采用了Linux
的epoll
模型。
1.2 NIO詳解
在連接數不多的情況下,傳統 I/O
模式編寫較為容易,使用上也較為簡單。但是隨着連接數的不斷增多,傳統 I/O
處理每個連接都需要消耗一個線程,而程序的效率,當線程數不多時是隨着線程數的增加而增加,但是到一定的數量之后,是隨着線程數的增加而減少的。所以 傳統阻塞式 I/O 的瓶頸在於不能處理過多的連接
。
非阻塞式 I/O
出現的目的就是為了解決這個瓶頸。非阻塞IO
處理連接的線程數和連接數沒有聯系,例如系統處理 10000
個連接,非阻塞 I/O 不需要啟動 10000
個線程,你可以用 1000 個,也可以用 2000 個線程來處理。因為非阻塞 IO 處理連接是異步的,當某個連接發送請求到服務器,服務器把這個連接請求當作一個請求事件
,並把這個事件
分配給相應的函數處理。我們可以把這個處理函數放到線程中去執行,執行完就把線程歸還,這樣一個線程就可以異步的處理多個事件。而阻塞式 I/O 的線程的大部分時間都被浪費在等待請求上了
I/O | NIO |
---|---|
面向流 | 面向緩沖 |
阻塞 IO | 非阻塞 IO |
無 | 選擇器 |
NIO 是基於塊 (Block ) 的,它以塊為基本單位處理數據。在 NIO 中,最為重要的兩個組件是緩沖Buffer 和通道 Channel 。緩沖是一塊連續的內存塊,是 NIO 讀寫數據的中轉地。通道標識緩沖數據的源頭或者目的地,它用於向緩沖讀取或者寫入數據,是訪問緩沖的接口。Channel 是一個雙向通道,即可讀,也可寫。Stream 是單向的。應用程序不能直接對 Channel 進行讀寫操作,而必須通過 Buffer 來進行,即 Channel 是通過 Buffer 來讀寫數據的。 |
|
使用 Buffer 讀寫數據一般遵循以下四個步驟: |
- 寫入數據到
Buffer
; - 調用
flip()
方法; - 從
Buffer
中讀取數據; - 調用
clear()
方法或者compact()
方法。
當向 Buffer
寫入數據時,Buffer
會記錄下寫了多少數據。一旦要讀取數據,需要通過 flip()
方法將 Buffer
從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到 Buffer
的所有數據。
一旦讀完了所有的數據,就需要清空緩沖區,讓它可以再次被寫入。有兩種方式能清空緩沖區:調用 clear() 或 compact()
方法。clear()
方法會清空整個緩沖區。compact()
方法只會清除已經讀過的數據。任何未讀的數據都被移到緩沖區的起始處,新寫入的數據將放到緩沖區未讀數據的后面。
Buffer
有多種類型,不同的 Buffer
提供不同的方式操作 Buffer
中的數據
1.2.1 Buffer讀寫數據
Buffer
寫數據有兩種情況:
- 從
Channel
寫到Buffer
,如例子中Channel
從文件中讀取數據,寫到Channel
; - 直接調用
put
方法,往里面寫數據。
從 Buffer
中讀取數據有兩種方式:
- 從
Buffer
讀取數據到Channel
; - 使用
get()
方法從Buffer
中讀取數據。
Buffer
的 rewin
方法將 position
設回 0
,所以可以重讀 Buffer
中的所有數據。limit
保持不變,仍然表示能從 Buffer
中讀取多少個元素(byte、char
等)。
1.2.2 Buffer和clear方法
clear()
和 compact()
方法
一旦讀完 Buffer
中的數據,需要讓 Buffer
准備好再次被寫入。可以通過 clear() 或 compact()
方法來完成。
如果調用的是 clear()
方法,position
將被設回 0
,limit
被設置成 capacity
的值。換句話說,Buffer
被清空了。Buffer
中的數據並未清除,只是這些標記告訴我們可以從哪里開始往 Buffer
里寫數據。
如果 Buffer
中有一些未讀的數據,調用 clear()
方法,數據將被遺忘
,意味着不再有任何標記會告訴你哪些數據被讀過,哪些還沒有。如果 Buffer
中仍有未讀的數據,且后續還需要這些數據,但是此時想要先寫些數據,那么使用 compact()
方法。compact()
方法將所有未讀的數據拷貝到 Buffer
起始處。然后將 position
設到最后一個未讀元素正后面。limit
屬性依然像 clear()
方法一樣,設置成 capacity
。現在 Buffer
准備好寫數據了,但是不會覆蓋未讀的數據。
1.2.3 Buffer參數
Buffer
有 3 個重要的參數:位置 (position
)、容量 (capacity
) 和上限 (limit
)。
capacity
是指 Buffer
的大小,在 Buffer
建立的時候已經確定。
limit
當 Buffer
處於寫模式,指還可以寫入多少數據;處於讀模式,指還有多少數據可以讀。
position
當 Buffer
處於寫模式,指下一個寫數據的位置;處於讀模式,當前將要讀取的數據的位置。每讀寫一個數據,position+1
,也就是limit
和 position
在 Buffer
的讀/寫時的含義不一樣。當調用 Buffer
的 flip
方法,由寫模式變為讀模式時,limit(讀)=position(寫)
,position(讀) =0
。
1.2.4 散射&聚集
NIO
提供了處理結構化數據的方法,稱之為散射 (Scattering
) 和聚集 (Gathering
)。
散射
是指將數據讀入
一組 Buffer
中,而不僅僅是一個。聚集
與之相反,指將數據寫入
一組 Buffer
中。
散射和聚集的基本使用方法和對單個 Buffer
操作時的使用方法相當類似。在散射讀取中,通道依次填充每個緩沖區。填滿一個緩沖區后,它就開始填充下一個,在某種意義上,緩沖區數組就像一個大緩沖區。在已知文件具體結構的情況下,可以構造若干個符合文件結構的 Buffer
,使得各個 Buffer
的大小恰好符合文件各段結構的大小。此時,通過散射讀的方式可以一次將內容裝配到各個對應的 Buffer
中,從而簡化操作。如果需要創建指定格式的文件,只要先構造好大小合適的 Buffer
對象,使用聚集寫的方式,便可以很快地創建出文件。
1.3 Java AIO
AIO
相關的類和接口:
java.nio.channels.AsynchronousChannel
:標記一個Channel
支持異步 IO 操作;java.nio.channels.AsynchronousServerSocketChannel
:ServerSocket
的AIO
版本,創建 TCP 服務端,綁定地址,監聽端口等;java.nio.channels.AsynchronousSocketChannel
:面向流的異步Socket Channel
,表示一個連接;java.nio.channels.AsynchronousChannelGroup
:異步Channel
的分組管理,目的是為了資源共享。一個AsynchronousChannelGroup
綁定一個線程池,這個線程池執行兩個任務:處理IO
事件和派發CompletionHandler
。AsynchronousServerSocketChannel
創建的時候可以傳入一個AsynchronousChannelGroup
,那么通過AsynchronousServerSocketChannel
創建的AsynchronousSocketChannel
將同屬於一個組,共享資源;java.nio.channels.CompletionHandler
:異步IO
操作結果的回調接口,用於定義在IO
操作完成后所作的回調工作。AIO
的API
允許兩種方式來處理異步操作的結果:返回的Future
模式或者注冊CompletionHandler
,推薦用CompletionHandler
的方式,這些handler
的調用是由AsynchronousChannelGroup
的線程池派發的。這里線程池的大小是性能的關鍵因素。
1.4 使用例子
1.4.1 散射聚集
使用散射和聚集讀寫結構化文件
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOScatteringandGathering {
public void createFiles(String TPATH){
try {
ByteBuffer bookBuf = ByteBuffer.wrap("java 性能優化技巧".getBytes("utf-8"));
ByteBuffer autBuf = ByteBuffer.wrap("test".getBytes("utf-8"));
int booklen = bookBuf.limit();
int autlen = autBuf.limit();
ByteBuffer[] bufs = new ByteBuffer[]{bookBuf,autBuf};
File file = new File(TPATH);
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
FileOutputStream fos = new FileOutputStream(file);
FileChannel fc = fos.getChannel();
fc.write(bufs);
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ByteBuffer b1 = ByteBuffer.allocate(booklen);
ByteBuffer b2 = ByteBuffer.allocate(autlen);
ByteBuffer[] bufs1 = new ByteBuffer[]{b1,b2};
File file1 = new File(TPATH);
try {
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
fc.read(bufs1);
String bookname = new String(bufs1[0].array(),"utf-8");
String autname = new String(bufs1[1].array(),"utf-8");
System.out.println(bookname+" "+autname);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args){
NIOScatteringandGathering nio = new NIOScatteringandGathering();
nio.createFiles("C:\\1.TXT");
}
}
1.4.2 I/O 的三種方式對比試驗
以下所示代碼對傳統 I/O
、基於 Byte
的 NIO
、基於內存映射的 NIO
三種方式進行了性能上的對比,使用一個有 400 萬數據的文件的讀、寫操作耗時作為評測依據。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class NIOComparator {
public void IOMethod(String TPATH){
long start = System.currentTimeMillis();
try {
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(new File(TPATH))));
for(int i=0;i<4000000;i++){
dos.writeInt(i);//寫入 4000000 個整數
}
if(dos!=null){
dos.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
try {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(new File(TPATH))));
for(int i=0;i<4000000;i++){
dis.readInt();
}
if(dis!=null){
dis.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
public void ByteMethod(String TPATH){
long start = System.currentTimeMillis();
try {
FileOutputStream fout = new FileOutputStream(new File(TPATH));
FileChannel fc = fout.getChannel();//得到文件通道
ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer
for(int i=0;i<4000000;i++){
byteBuffer.put(int2byte(i));//將整數轉為數組
}
byteBuffer.flip();//准備寫
fc.write(byteBuffer);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
FileInputStream fin;
try {
fin = new FileInputStream(new File(TPATH));
FileChannel fc = fin.getChannel();//取得文件通道
ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer
fc.read(byteBuffer);//讀取文件數據
fc.close();
byteBuffer.flip();//准備讀取數據
while(byteBuffer.hasRemaining()){
byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());//將 byte 轉為整數
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
public void mapMethod(String TPATH){
long start = System.currentTimeMillis();
//將文件直接映射到內存的方法
try {
FileChannel fc = new RandomAccessFile(TPATH,"rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, 4000000*4).asIntBuffer();
for(int i=0;i<4000000;i++){
ib.put(i);
}
if(fc!=null){
fc.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
try {
FileChannel fc = new FileInputStream(TPATH).getChannel();
MappedByteBuffer lib = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
lib.asIntBuffer();
while(lib.hasRemaining()){
lib.get();
}
if(fc!=null){
fc.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
public static byte[] int2byte(int res){
byte[] targets = new byte[4];
targets[3] = (byte)(res & 0xff);//最低位
targets[2] = (byte)((res>>8)&0xff);//次低位
targets[1] = (byte)((res>>16)&0xff);//次高位
targets[0] = (byte)((res>>>24));//最高位,無符號右移
return targets;
}
public static int byte2int(byte b1,byte b2,byte b3,byte b4){
return ((b1 & 0xff)<<24)|((b2 & 0xff)<<16)|((b3 & 0xff)<<8)|(b4 & 0xff);
}
public static void main(String[] args){
NIOComparator nio = new NIOComparator();
nio.IOMethod("c:\\1.txt");
nio.ByteMethod("c:\\2.txt");
nio.ByteMethod("c:\\3.txt");
}
}
1.4.3 DirectBuffer VS ByteBuffer
NIO
的 Buffer
還提供了一個可以直接訪問系統物理內存的類 DirectBuffer
。DirectBuffer
繼承自 ByteBuffer
,但和普通的 ByteBuffer
不同。
普通的 ByteBuffer
仍然在 JVM
堆上分配空間,其最大內存受到最大堆的限制,而 DirectBuffer
直接分配在物理內存上,並不占用堆空間。在對普通的 ByteBuffer
訪問時,系統總是會使用一個內核緩沖區
進行間接的操作。而 DirectrBuffer
所處的位置,相當於這個內核緩沖區
。
因此,使用 DirectBuffer
是一種更加接近系統底層的方法,所以,它的速度比普通的 ByteBuffer
更快。DirectBuffer
相對於ByteBuffer
而言,讀寫訪問速度快很多,但是創建和銷毀 DirectrBuffer
的花費卻比 ByteBuffer
高。DirectBuffer 與 ByteBuffer 相比較的代碼如下所示
import java.nio.ByteBuffer;
public class DirectBuffervsByteBuffer {
public void DirectBufferPerform(){
long start = System.currentTimeMillis();
ByteBuffer bb = ByteBuffer.allocateDirect(500);//分配 DirectBuffer
for(int i=0;i<100000;i++){
for(int j=0;j<99;j++){
bb.putInt(j);
}
bb.flip();
for(int j=0;j<99;j++){
bb.getInt(j);
}
}
bb.clear();
long end = System.currentTimeMillis();
System.out.println(end-start);
start = System.currentTimeMillis();
for(int i=0;i<20000;i++){
ByteBuffer b = ByteBuffer.allocateDirect(10000);//創建 DirectBuffer
}
end = System.currentTimeMillis();
System.out.println(end-start);
}
public void ByteBufferPerform(){
long start = System.currentTimeMillis();
ByteBuffer bb = ByteBuffer.allocate(500);//分配 DirectBuffer
for(int i=0;i<100000;i++){
for(int j=0;j<99;j++){
bb.putInt(j);
}
bb.flip();
for(int j=0;j<99;j++){
bb.getInt(j);
}
}
bb.clear();
long end = System.currentTimeMillis();
System.out.println(end-start);
start = System.currentTimeMillis();
for(int i=0;i<20000;i++){
ByteBuffer b = ByteBuffer.allocate(10000);//創建 ByteBuffer
}
end = System.currentTimeMillis();
System.out.println(end-start);
}
public static void main(String[] args){
DirectBuffervsByteBuffer db = new DirectBuffervsByteBuffer();
db.ByteBufferPerform();
db.DirectBufferPerform();
}
}
運行結果
920
110
531
390
可知,頻繁創建和銷毀 DirectBuffer
的代價遠遠大於在堆上分配內存空間。
使用參數-XX:MaxDirectMemorySize=200M –Xmx200M
在 VM Arguments
里面配置最大 DirectBuffer
和最大堆空間
,代碼中分別請求了 200M
的空間,如果設置的堆空間過小,例如設置 1M
,會拋出錯誤如下所示
Error occurred during initialization of VM
Too small initial heap for new size specified
1.4.4 對DirectBuffer監控代碼
DirectBuffer
的信息不會打印在 GC
里面,因為 GC
只記錄了堆空間的內存回收。可以看到,由於 ByteBuffer
在堆上分配空間,因此其 GC
數組相對非常頻繁,在需要頻繁創建 Buffer
的場合,由於創建和銷毀 DirectBuffer
的代碼比較高昂,不宜使用 DirectBuffer
。但是如果能將DirectBuffer
進行復用,可以大幅改善系統性能
import java.lang.reflect.Field;
public class monDirectBuffer {
public static void main(String[] args){
try {
Class c = Class.forName("java.nio.Bits");//通過反射取得私有數據
Field maxMemory = c.getDeclaredField("maxMemory");
maxMemory.setAccessible(true);
Field reservedMemory = c.getDeclaredField("reservedMemory");
reservedMemory.setAccessible(true);
synchronized(c){
Long maxMemoryValue = (Long)maxMemory.get(null);
Long reservedMemoryValue = (Long)reservedMemory.get(null);
System.out.println("maxMemoryValue="+maxMemoryValue);
System.out.println("reservedMemoryValue="+reservedMemoryValue);
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
運行輸出
maxMemoryValue=67108864
reservedMemoryValue=0
由於 NIO
使用起來較為困難,所以許多公司推出了自己封裝 JDK NIO
的框架,例如 Apache 的 Mina
,JBoss 的 Netty
,Sun 的 Grizzly
等等,這些框架都直接封裝了傳輸層的 TCP
或 UDP
協議,其中 Netty
只是一個 NIO
框架,它不需要 Web
容器的額外支持,也就是說不限定 Web
容器
1.4.5 AIO使用例子
服務端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
public class SimpleServer {
public SimpleServer(int port) throws IOException {
final AsynchronousServerSocketChannel listener =
AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(port));
//監聽消息,收到后啟動 Handle 處理模塊
listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
public void completed(AsynchronousSocketChannel ch, Void att) {
listener.accept(null, this);// 接受下一個連接
handle(ch);// 處理當前連接
}
@Override
public void failed(Throwable exc, Void attachment) {
// TODO Auto-generated method stub
}
});
}
public void handle(AsynchronousSocketChannel ch) {
ByteBuffer byteBuffer = ByteBuffer.allocate(32);//開一個 Buffer
try {
ch.read(byteBuffer).get();//讀取輸入
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byteBuffer.flip();
System.out.println(byteBuffer.get());
// Do something
}
}
客戶端程序
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class SimpleClientClass {
private AsynchronousSocketChannel client;
public SimpleClientClass(String host, int port) throws IOException,
InterruptedException, ExecutionException {
this.client = AsynchronousSocketChannel.open();
Future<?> future = client.connect(new InetSocketAddress(host, port));
future.get();
}
public void write(byte b) {
ByteBuffer byteBuffer = ByteBuffer.allocate(32);
System.out.println("byteBuffer="+byteBuffer);
byteBuffer.put(b);//向 buffer 寫入讀取到的字符
byteBuffer.flip();
System.out.println("byteBuffer="+byteBuffer);
client.write(byteBuffer);
}
}
Main 函數
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class AIODemoTest {
@Test
public void testServer() throws IOException, InterruptedException {
SimpleServer server = new SimpleServer(9021);
Thread.sleep(10000);//由於是異步操作,所以睡眠一定時間,以免程序很快結束
}
@Test
public void testClient() throws IOException, InterruptedException, ExecutionException {
SimpleClientClass client = new SimpleClientClass("localhost", 9021);
client.write((byte) 11);
}
public static void main(String[] args){
AIODemoTest demoTest = new AIODemoTest();
try {
demoTest.testServer();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
demoTest.testClient();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}