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。

相关文章
相关标签/搜索