JAVA NIO系列(二) Channel解讀

Channel就是一個通道,用於傳輸數據,兩端分別是緩衝區和實體(文件或者套接字),通道的特色(也是NIO的特色):通道中的數據老是要先讀到一個緩衝區,或者老是要從一個緩衝區中讀入。java

Channel的分類

1) FileChannel:從文件中讀寫數據安全

2) SocketChannel:經過TCP協議讀寫網絡中的數據服務器

3) ServerSocketChannel:在服務器端能夠監聽新進來的TCP鏈接,像WEB服務器那樣,對每個新進來的請求建立一個SocketChannel網絡

4) DatagramChannel:經過UDP協議讀寫網絡中的數據併發

上面衆多的分類,是對應了不一樣的實體,這些通道包括了文件IO、TCP和UDP網絡IO。dom

 

下面來看看Channel的源碼:socket

 1 public interface Channel extends Closeable {
 2 
 8     public boolean isOpen();
 9 
27     public void close() throws IOException;
28 
29 }

從這裏咱們能夠看到,Channel接口只提供了關閉通道和檢測通道是否打開這兩個方法,剩下方法的都是由子接口和實現類來定義提供。spa

    

咱們選擇其中幾個來看看這些接口的源碼:.net

1 public interface WritableByteChannel
2     extends Channel
3 {
4 
5     public int write(ByteBuffer src) throws IOException;
6 
7 }
public interface ReadableByteChannel extends Channel 
{
public int read(ByteBuffer dst) throws IOException; }
public interface ByteChannel
    extends ReadableByteChannel, WritableByteChannel
{

}

前面我提到過:通道能夠只讀、只寫或者同時讀寫,由於Channel類能夠只實現只讀接口ReadableByteChannel或者只實現只寫接口WritableByteChannel而咱們經常使用的Channel類FileChannel、SocketChannel、DatagramChannel是雙向通訊的, 由於實現了ByteChannel接口。線程

Channel的獲取

IO在廣義上能夠分爲:文件IO和網絡IO。文件IO對應的通道爲FileChannel,而網絡IO對應的通道則有三個:SocketChannel、ServerSoketChannel和DatagramChannel。

1、文件通道

FileChannel對象不能直接建立,只能經過FileInputStream、OutputStream、RandomAccessFile對象的getChannel()來獲取,如:

FileInputStream fis = new FileInputStream("c:/in.txt");
FileChannel fic = fis.getChannel();

 FileChannel沒法設置爲非阻塞模式,它老是運行在阻塞模式下。

1)使用通道讀取文件

 1 public class NIOFileReadTest
 2 {
 3     public static void main(String[] args) throws IOException
 4     {
 5         RandomAccessFile raf = new RandomAccessFile("D:/in.txt","rw");
 6         FileChannel fis = raf.getChannel();
 7         ByteBuffer buffer = ByteBuffer.allocate(1024);
 8         fis.read(buffer);
 9         buffer.flip();
10         while(buffer.hasRemaining())
11         {
12             System.out.print((char)buffer.get());
13         }
14         buffer.clear();
15         fis.close();
16     }
17 }

執行結果:

FileChannel
ByteBuffer
SelectorPicked

2)使用通道寫入文件

public class NIOFileWriteTest
{
    public static void main(String[] args) throws Exception
    {
        FileOutputStream fos = new FileOutputStream("d:/out.txt");
        FileChannel fc = fos.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.clear();
        String str = "Channel";
        buffer.put(str.getBytes());
        buffer.flip();
        while(buffer.hasRemaining())
        {
            fc.write(buffer);
        }
        fc.close();
        fos.close();
    }
}

在這裏老是要記住channel是要關閉的。

ByteBuffer中的方法我在下一章再詳細介紹,這裏只要注意這點便可:通道只能使用ByteBuffer,不論是讀仍是寫,通道都要對接緩衝區

3)通道的經常使用方法

position();返回通道的文件位置

position(long newPosition):設置通道的文件位置

將上面讀文件的程序修改下,來觀察這幾個方法:

 1 public class NIOFileReadTest
 2 {
 3     public static void main(String[] args) throws IOException
 4     {
 5         RandomAccessFile raf = new RandomAccessFile("D:/in.txt","rw");
 6         FileChannel fis = raf.getChannel();
 7         System.out.println("此通道文件的總長度:" +fis.size());
 8         //當前通道的文件位置
 9         long position = fis.position();
10         System.out.println("通道當前的位置:" + position);
11         //設置新的通道文件位置,從這個位置開始讀取
12         fis.position(position + 8);
13         ByteBuffer buffer = ByteBuffer.allocate(50);
14         fis.read(buffer);
15         buffer.flip();
16         while(buffer.hasRemaining())
17         {
18             System.out.print((char)buffer.get());
19         }
20         buffer.clear();
21         fis.close();
22     }
23 }

執行結果:

此通道文件的總長度:33
通道當前的位置:0
nel
ByteBuffer
Selector

FileChannel是線程安全的,能夠多個線程在同一個實例上併發操做,可是其中有些方法(改變文件通道位置或者文件大小的方法)必須是單線程操做。

2、網絡通道

 SocketChannel是一個鏈接到TCP套接字的通道,獲取的方式有兩種:

一、打開一個SocketChannel並鏈接到互聯網上某臺服務器。

二、一個新鏈接到達ServerSocketChannel時,會建立一個SocketChannel。

上面這兩種模式跟IO的Socket、ServerSocket相似,下面分別來看看客戶端和服務器端:

1、SocketChannel

從通道中讀取數據

 1 public class SocketChannelTest
 2 {
 3     public static void main(String[] args) throws Exception
 4     {
 5         //獲取socket通道
 6         SocketChannel sc = SocketChannel.open();
 7         //設置爲非阻塞模式
 8         sc.configureBlocking(false);
 9         //創建鏈接,非阻塞模式下,該方法可能在鏈接創建以前就返回了
10         sc.connect(new InetSocketAddress("wap.cmread.com",80));
11         //判斷鏈接是否創建
12         while(!sc.finishConnect())
13         {
14             System.out.println("鏈接未創建");
15             Thread.sleep(5);
16         }
17         ByteBuffer buffer = ByteBuffer.allocate(48);
18         int byteRead = sc.read(buffer);
19         System.out.println(byteRead);
20         sc.close();
21         buffer.clear();  
22     }
23 }

執行結果;

鏈接未創建
鏈接未創建
0

一、第六、7行是獲取一個socket通道,而且設置爲非阻塞模式。

二、因爲是非阻塞模式,通道在調用方法connect/read/writer這三個方法時,會出現這些狀況:鏈接未創建,connect方法就返回了;還沒有讀取任何數據時,read方法就返回;還沒有寫出任何內容時,writer就返回

三、在12行的循環代碼中,是判斷鏈接是否創建,從執行結果來看,循環執行了兩次鏈接才創建(在循環裏線程還有休眠)。

四、因爲只是創建了鏈接,通道里面其實沒有任何的數據。

五、第18行調用read方法,因爲是非阻塞模式,因此在並未讀取任何數據的狀況下就返回0(儘管通道里面沒有數據)。

將數據寫入通道

 1 public class SocketChannelTest
 2 {
 3     public static void main(String[] args) throws Exception
 4     {
 5         SocketChannel sc = SocketChannel.open();
 6         String str = "non-blocking socket channel";
 7         ByteBuffer buffer = ByteBuffer.allocate(100);
 8         buffer.put(str.getBytes());
 9         buffer.flip();
10         while(buffer.hasRemaining())
11         {
12             sc.write(buffer);
13         }
14         sc.close();
15         buffer.clear();
16     }
17 }

一、SocketChannel.write()方法的調用是在一個while循環中的。Write()方法沒法保證能寫多少字節到SocketChannel。因此,咱們重複調用write()直到Buffer沒有要寫的字節爲止。

2、ServerSocketChannel

ServerSocketChannel是一個能夠監聽新進來的TCP鏈接的通道。

 1 public class ServerSocketChannelTest
 2 {
 3     public static void main(String[] args) throws Exception
 4     {
 5         ServerSocketChannel ssc = ServerSocketChannel.open();
 6         ssc.socket().bind(new InetSocketAddress(80));
 7         ssc.configureBlocking(false);
 8         while(true)
 9         {
10             SocketChannel sc = ssc.accept();
11             if(null != sc)
12             {
13                 //do something;
14             }
15         }
16     }
17 }

一、第五、六、7行,獲取一個ServerSocketChannel,而且監聽80端口,設置爲非阻塞模式。

二、經過accept方法監聽新接入進來的鏈接,這個方法會返回一個包含新進來的鏈接的SocketChannel(服務器端的通道的獲取方式)。若是是阻塞模式,該方法會一直阻塞直到有新的鏈接進來。若是是非阻塞模式,則accept方法會馬上返回,返回值是null。

三、第11行,是由於在非阻塞模式下,須要檢查SocketChannel是否爲null。

3、socket通道與socket

1 ServerSocketChannel ssc = ServerSocketChannel.open();
2 ServerSocket socket = ssc.socket();
3 ServerSocketChannel ssc1 = socket.getChannel();

一、從這代碼片斷能夠大概看到這樣一種關係:全部socket通道(SocketChannel/ServerSocketChanne/DatagramSocketChannel)在被實例化以後,都是伴隨生成對應的socket對象,就是前面IO章節介紹的java.net類(Socket/ServerSocket/DatagramSocket)。經過通道類的socket方法來獲取。

二、java.net類(Socket/ServerSocket/DatagramSocket)如今能夠經過getChannel方法來獲取對應的通道。前提是這些socket對象不是使用傳統方式(直接實例化)建立的。不然它就沒有關聯的socket通道,調用getChannel方法返回老是null。

相關文章
相關標籤/搜索