// 2. 通过输出流获取Channel
FileChannel channel = fos.getChannel();
// 3. 创建一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("hello".getBytes());
// 4. 切换读模式(limit=position=5;position=0)
buffer.flip();
// 5. 将Buffer的数据读出来,写入到Channel中
int len = channel.write(buffer); //
System.out.println("写出的有效字节个数: " + len); // 5
len = channel.write(buffer);
System.out.println("写出的有效字节个数: " + len); // 0
channel.close();
fos.close();
}
}
**注意:channel.write()方法是将数据从Buffer中读取出来,然后写入到Channel中,这对Buffer本质上是一次读操作,我们对Buffer的任何读写操作都会造成Buffer中的position位移;**
再次测试:
@Test
public void writer_02() throws Exception {
// 1. 创建一个输出流
FileOutputStream fos = new FileOutputStream("001.txt");
// 2. 通过输出流获取Channel
FileChannel channel = fos.getChannel();
// 3. 创建一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.err.println(buffer); // position=0,limit=capacity=1024
buffer.put("hello".getBytes());
System.err.println(buffer); // position=5,limit=capacity=1024
// 4. 切换读模式(limit=position=5;position=0)
buffer.flip();
System.err.println(buffer); // position=0,limit=5,capacity=1024
// 5. 将Buffer的数据读出来(也会造成position的位移),写入到Channel中
int len = channel.write(buffer); //
System.out.println("写出的有效字节个数: " + len); // 5
len = channel.write(buffer);
System.out.println("写出的有效字节个数: " + len); // 0
System.err.println(buffer); // position=5,limit=5,capacity=1024
channel.close();
fos.close();
}
执行程序,查看控制台:

案例2:通过Channel读取数据
@Test
public void reader() throws Exception {
// 1. 创建一个输入流
FileInputStream fis = new FileInputStream("001.txt");
// 2. 通过输入流获取Channel
FileChannel channel = fis.getChannel();
// 3. 创建一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 4. 从Channel中读取数据,写入到Buffer中
int len = channel.read(buffer);
System.out.println("读取到的有效字节个数: " + len); // 5
len = channel.read(buffer);
System.out.println("读取到的有效字节个数: " + len); // -1
// limit=position,position=0
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data); // 从Buffer中读取数据
System.out.println(new String(data, 0, data.length));
channel.close();
fis.close();
}
**注意:和write方法一样,channel调用read方法是将数据从Channel读取出来,往Buffer中写入,这对Buffer来说本质上是一种写的操作,我们对Buffer的任何读写操作都会造成Buffer中的position位移;**
再次测试:
@Test
public void reader_01() throws Exception {
// 1. 创建一个输入流
FileInputStream fis = new FileInputStream("001.txt"); // 文件内容: hello
// 2. 通过输入流获取Channel
FileChannel channel = fis.getChannel();
// 3. 创建一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.err.println(buffer); // position=0,limit=capacity=1024
// 4. 从Channel中读取数据,写入到Buffer中(会造成position的位移)
channel.read(buffer);
System.err.println(buffer); // position=5,limit=capacity=1024
// limit=position=5,position=0,capacity=1024
buffer.flip();
System.err.println(buffer); // position=0,limit=5,capacity=1024
byte[] data = new byte[buffer.limit()];
// 从Buffer中读取数据
buffer.get(data);
System.err.println(buffer); // position=5,limit=5,capacity=1024
System.out.println(new String(data, 0, data.length));
channel.close();
fis.close();
}
执行程序,查看控制台:

* 案例3-使用FileChannel拷贝文件:
@Test
public void copy() throws Exception {
FileInputStream fis = new FileInputStream(“100.png”);
FileOutputStream fos = new FileOutputStream(“200.png”);
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取Channel中的数据,写入到Buffer中
while (inChannel.read(buffer) != -1) {
// limit=position,position=0
buffer.flip();
// 从Buffer中读取数据,写入Channel中
outChannel.write(buffer);
// limit=capacity,position=0
buffer.clear();
}
inChannel.close();
outChannel.close();
fis.close();
fos.close();
}
#### 5.1.2 FileChannel的常用方法
* `FileChannel truncate(long s)`:将此通道的文件截取为给定大小
* `long size()`:返回此通道的文件的当前大小
* `long position()`:返回该Channel目前所在的文件位置;
* `FileChannel position(long p)`:设置该Channel目前所在的文件位置;
* `public void force(boolean metaData)`:将当前Channel中的数据强制写入到磁盘中
* `public long transferFrom(ReadableByteChannel src, long position, long count)`:从src的position位置开始读取,读取count个字节到当前Channel中
* `public long transferTo(long position, long count,WritableByteChannel target)`:从当前Channel的position位置开始读取,读取count个字节到target中
* `long write(ByteBuffer[] srcs)` 将ByteBuffer[]到中的数据全部写入(聚集)到 Channel
* `long read(ByteBuffer[] dsts)` 将Channel到中的数据读取出来,然后全部写入(分散)到ByteBuffer[]
---
##### 1)truncate
* `FileChannel truncate(long s)`:将此通道的文件截取为给定大小;
可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。
@Test
public void truncate() throws Exception {
FileInputStream stream = new FileInputStream("001.txt"); // 文件内容: hello
// FileOutputStream stream = new FileOutputStream(“001.txt”); // 文件内容: hello
// 创建流(可读可写)
// RandomAccessFile stream = new RandomAccessFile(“001.txt”, “rw”); // 文件内容: hello
// 获取Channel
FileChannel channel = stream.getChannel();
// 将Channel的文件截取为2
channel.truncate(2); // 使用FileInputStream出现: java.nio.channels.NonWritableChannelException
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer); // 使用FileOutputStream出现: java.nio.channels.NonReadableChannelException
System.out.println(new String(buffer.array(), 0, buffer.array().length)); // he
stream.close();
channel.close();
}
>
> Tips:FileInputStream只能读取数据,因此使用FileInputStream获取的FileChannel也只能读取数据;同理FileOutputStream只能写出数据,使用FileOutputStream获取FileChannel也只能写出数据;因此上述案例中使用`RandomAccessFile`,即可读又可写;
>
>
>
##### 2)size
FileChannel实例的size()方法将返回该实例所关联文件的大小。
* `long size()`:返回此通道的文件的当前大小
@Test
public void size() throws Exception {
FileInputStream fis = new FileInputStream("001.txt");
FileChannel channel = fis.getChannel();
System.out.println(channel.size()); // 5
}
##### 3)position
* `long position()`:返回该Channel目前所在的文件位置;
有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取FileChannel的当前位置。也可以通过调用position(long pos)方法设置FileChannel的当前位置。
* 测试position:
package com.dfbz.channel;
import org.junit.Test;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_常用方法 {
@Test
public void position() throws Exception {
FileInputStream fis = new FileInputStream("001.txt");
FileChannel channel = fis.getChannel();
// 获取此Channel的读写位置(默认为0)
System.out.println(channel.position()); // 0
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从Channel中读取数据到buffer中,读取了5个有效字节
channel.read(buffer);
System.out.println("buffer中的内容: " + new String(buffer.array(), 0, buffer.array().length));
System.out.println("---------------------");
// limit=capacity,position=0
buffer.clear();
System.out.println(channel.position()); // 5
// 将channel的读取位置设置为2
channel.position(2);
channel.read(buffer);
System.out.println("buffer中的内容: " + new String(buffer.array(), 0, buffer.array().length));
}
}
>
> Tips:如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志。
>
>
>
**如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。**
* 测试position-2:
@Test
public void position2() throws Exception {
// 创建流(可读可写)
RandomAccessFile raf = new RandomAccessFile("001.txt", "rw"); // 文件内容: hello
// 获取Channel
FileChannel channel = raf.getChannel();
// 将position设置为size+10(空隙)
channel.position(channel.size()+10);
// 往Channel写出数据
channel.write(ByteBuffer.wrap("abc".getBytes()));
raf.close();
channel.close();
}
001.txt文件内容如下:

##### 4)force
* `public void force(boolean metaData)`:将当前Channel中的数据强制写入到磁盘中
FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。
另外,force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上
* 测试代码:
@Test
public void force() throws Exception {
// 创建流(可读可写)
RandomAccessFile raf = new RandomAccessFile("001.txt", "rw"); // 文件内容: hello
// 获取Channel
FileChannel channel = raf.getChannel();
channel.write(ByteBuffer.wrap("Hello Everyone!".getBytes()));
/\*
将内存中的数据强制写入到磁盘中
true: 将文件元信息(权限信息等)写入磁盘
false: 不写入文件元信息到磁盘
*/
channel.force(true);
raf.close();
channel.close();
}
##### 5)transferFrom
* `public long transferFrom(ReadableByteChannel src, long position, long count)`:从src的position位置开始读取,读取count个字节到当前Channel中
FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中
@Test
public void transferFrom() throws Exception {
RandomAccessFile fromFile = new RandomAccessFile("001.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("002.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
// 从fromChannel的position位置开始读取,读取count个字节到toChannel中
toChannel.transferFrom(fromChannel, position, count);
}
##### 6)transferTo
* `public long transferTo(long position, long count,WritableByteChannel target)`:从当前Channel的position位置开始读取,读取count个字节到target中
transferTo()方法将数据从FileChannel传输到其他的channel中
@Test
public void transferTo() throws Exception {
RandomAccessFile fromFile = new RandomAccessFile("001.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("002.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
// 从fromChannel的position位置开始读取,读取count到toChannel中
fromChannel.transferTo(position, count, toChannel);
}
#### 5.1.3 聚集和分散
* 聚集(gather):写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。
* 分散(scatter):从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。
* `long write(ByteBuffer[] srcs)` 将ByteBuffer[]到中的数据全部写入(聚集)到 Channel
* `long read(ByteBuffer[] dsts)` 将Channel到中的数据读取出来,然后全部写入(分散)到ByteBuffer[]
---
* Gathering Writes(聚集):是指数据从多个buffer写入到同一个channel。如下图描述:

* Scattering Reads(分散):是指数据从一个channel读取到多个buffer中。如下图描述:

* 测试代码:
package com.dfbz.channel;
import org.junit.Test;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03_聚集和分散 {
// 聚集
@Test
public void gather() throws IOException {
RandomAccessFile raf = new RandomAccessFile("004.txt", "rw");
FileChannel channel = raf.getChannel();
ByteBuffer header = ByteBuffer.wrap("{'content-Type':'application/json'}".getBytes());
ByteBuffer body = ByteBuffer.wrap("{'username':'admin','password':'123'}".getBytes());
ByteBuffer[] bufferArray = {header, body};
// 将多个Buffer聚集到一起写入到channel中
channel.write(bufferArray);
channel.close();
raf.close();
}
// 分散
@Test
public void test2() throws IOException {
RandomAccessFile raf = new RandomAccessFile("004.txt", "rw");
FileChannel channel = raf.getChannel();
ByteBuffer header = ByteBuffer.allocate("{'content-Type':'application/json'}".getBytes().length);
ByteBuffer body = ByteBuffer.allocate("{'username':'admin','password':'123'}".getBytes().length);
ByteBuffer[] bufferArray = {header, body};
// 将channel中的数据分散读取,然后逐个写入到每个Buffer中
channel.read(bufferArray);
for (ByteBuffer buffer : bufferArray) {
System.out.println(new String(buffer.array(),0,buffer.array().length));
}
channel.close();
raf.close();
}
}
### 5.2 DatagramChannel
#### 5.2.1 DatagramChannel简介
DatagramChannel是用于UDP编程的Channel;获取到了DatagramChannel之后,可以使用Channel直接发送Buffer数据;因为UDP是无连接的网络协议,因此使用DatagramChannel发送的Buffer数据在发送时都会被封装成UDP报文,并且存在UDP协议的特性;
* **发送和接收报文**:
在使用DatagramChannel发送数据时,必须通过`InetSocketAddress`类来指定接收端的地址和端口;而在接收数据时,接收端的Channel必须先通过`InetSocketAddress`类绑定一个地址和端口;
* **读取和写入数据**:
DatagramChannel不仅可以发送报文和接收报文,还可以读取DatagreamChannel中的数据,或往DatagreamChannel中写入数据;与发送和接收不同,在使用DatagramChannel发送或接收时,DatagramChannel充当一个接收器/发送器的角色,**自己本身并不存储那些数据**;而是将数据接收到一个Buffer中,而在使用DatagramChannel读取/写入数据时,数据可以从Buffer中读取到Channel中,也可以从Channel写出到Buffer;
#### 5.2.2 DatagramChannel的获取
在Java NIO中,我们可以通过DatagreamChannel来直接打开一个Channel,也可以DatagreamSocket可以获取到一个属于该Socket的Channel,**但该Socket必须是由Channel获取的Socket**;这两种方式获取到的Channel是同一个;
* DatagreamChannel方法:
+ `public static DatagramChannel open()` :打开一个基于UDP协议的Channel管道;
+ `public DatagramSocket socket()`:通过Channel获取一个Socket;
* DatagreamSocket方法:
+ `public DatagramChannel getChannel()`:获取该Socket的Channel管道;
测试代码:
package com.dfbz.channel.datagramChannel;
import org.junit.Test;
import java.net.DatagramSocket;
import java.nio.channels.DatagramChannel;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_DatagramChannel的获取 {
@Test
public void test() throws Exception {
// 通过DatagramChannel打开一个Channel
DatagramChannel channel = DatagramChannel.open();
// 通过channel也可以获取一个socket
DatagramSocket socket = channel.socket();
// 通过socket也可以获取Channel
DatagramChannel channel2 = socket.getChannel();
System.out.println(channel2.getClass()); // class sun.nio.ch.DatagramChannelImpl
System.out.println(channel == channel2); // true
}
@Test
public void test2() throws Exception {
DatagramSocket socket = new DatagramSocket(9999);
// 不能通过DatagramSocket来获取channel
DatagramChannel channel = socket.getChannel();
System.out.println(channel); // null
}
}
#### 5.2.3 DatagramChannel的常用方法
* `public DatagramChannel bind(SocketAddress local)`:绑定当前客户端的地址和端口,其他Channel向当前Channel发送数据时指定的地址。**在接收UDP报文时,必须先绑定;**
* `public SocketAddress receive(ByteBuffer dst)`:接收UDP报文,将接收到的UDP报文赋值给dst;
* `public int send(ByteBuffer src, SocketAddress target)`:发送一个UDP报文;并指定报文要发送的地址和端口
* `public DatagramChannel connect(SocketAddress remote)`:用于连接其他的DatagramChannel ,DatagramChannel之间建立连接后可以相互读取/写入数据;
>
> Tips:由于UDP是无连接的,使用connect方法连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据。因此即使是连接的地址不存在,也不会报错;
>
>
>
##### 1)发送和接收
测试代码:
package com.dfbz.channel.datagramChannel;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_发送和接收 {
/\*\*
* 发送端
*
* @throws Exception
*/
@Test
public void sender() throws Exception {
// 通过DatagramChannel打开一个Channel
DatagramChannel channel = DatagramChannel.open();
// 往该Channel写入数据
channel.send(ByteBuffer.wrap("hello".getBytes()), new InetSocketAddress("127.0.0.1", 9999));
channel.close();
}
/\*\*
* 接收端
*
* @throws Exception
*/
@Test
public void receive() throws Exception {
// 通过DatagramChannel打开一个Channel
DatagramChannel channel = DatagramChannel.open();
// 绑定地址(用于接收该地址发送过来的数据)
channel.bind(new InetSocketAddress("127.0.0.1", 9999));
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 使用Buffer接收报文
channel.receive(buffer);
System.out.println(new String(buffer.array(), 0, buffer.array().length));
}
}
##### 2)读取和写入

* 读取端:
package com.dfbz.channel.datagramChannel;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03_读取端 {
public static void main(String[] args) throws Exception {
// 获取Channel
DatagramChannel channel = DatagramChannel.open();
/\*
绑定一个地址
-
用于接收该地址发送的UDP报文
-
用于其他DatagramChannel与当前Channel建立连接(逻辑连接),待会可以使用Channel从127.0.0.1的9999端口读取数据
*/
channel.bind(new InetSocketAddress(“127.0.0.1”, 9999));/\*
连接一个地址:
-
建立一个UDP逻辑连接,如果需要读取127.0.0.2主机的数据则必须建立逻辑连接
-
建立好逻辑连接后,可以使用channel向127.0.0.2主机写入数据
*/
channel.connect(new InetSocketAddress(“127.0.0.2”, 9999));System.out.println("开始接收数据: "); System.out.println("----------------"); // 创建Buffer,用读取Channel中的数据 ByteBuffer readBuffer = ByteBuffer.allocate(1024); // position=0,limit=1024,capacity=1024 while (true) { // 将数据从Channel中读取出来,写入到readBuffer中 channel.read(readBuffer); // 预备下次一次从Channel读出数据写入到Buffer中[position=0,limit=capacity] readBuffer.clear(); System.out.println("接收到来自【" + channel.getRemoteAddress() + "】的数据: " + new String(readBuffer.array(), 0, readBuffer.array().length)); }
}
}
* 写入端:
package com.dfbz.channel.datagramChannel;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo04_写入端 {
public static void main(String[] args) throws Exception {
// 获取Channel
DatagramChannel channel = DatagramChannel.open();
/\*
绑定一个地址
-
用于接收该地址发送的UDP报文
-
用于其他DatagramChannel与当前Channel建立连接(逻辑连接),待会可以使用Channel从127.0.0.2的9999端口读取数据
*/
channel.bind(new InetSocketAddress(“127.0.0.2”, 9999));// 连接一个地址: 与指定的地址建立逻辑连接,用于向这个地址发送数据,待会可以使用Channel向写入数据到127.0.0.1的9999端口 channel.connect(new InetSocketAddress("127.0.0.1", 9999)); // 创建Buffer ByteBuffer writeBuffer = ByteBuffer.allocate(1024); // position=0,limit=1024,capacity=1024 // 获取一个扫描器 Scanner scanner = new Scanner(System.in); while (true) { System.out.println("请输入数据: "); // 接收控制台输入的数据 String str = scanner.nextLine(); // 将控制台输入的数据添加到buffer中 writeBuffer.put(str.getBytes()); // buffer的position会进行位移 // limit=position,position=0 writeBuffer.flip(); // 从Buffer中读取数据出来,往Channel中写入数据 // buffer的position会进行位移 channel.write(writeBuffer); // position=0,limit=capacity(预备下一次写入) writeBuffer.clear(); System.out.println("使用Channel向【" + channel.getRemoteAddress() + "】发送了数据: " + str); }
}
}
使用一个Channel进行读取和写入:
package com.dfbz.channel.datagramChannel;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo05_读取和写入 {
/\*\*
* 读取/写入数据
*
* @throws Exception
*/
@Test
public void readerAndWriter() throws Exception {
// 获取Channel
DatagramChannel channel = DatagramChannel.open();
/\*
绑定一个端口(本机)
-
接收127.0.0.1主机9999端口发送的UDP报文
-
用于其他DatagramChannel与当前Channel建立连接(逻辑连接),待会可以使用Channel从127.0.0.1的9999端口读取数据
*/
channel.bind(new InetSocketAddress(“127.0.0.1”, 9999));/\*
连接一个地址:
-
建立一个UDP逻辑连接,如果需要读取127.0.0.2主机的数据则必须建立逻辑连接
-
建立好逻辑连接后,可以使用channel向127.0.0.2主机写入数据
*/
channel.connect(new InetSocketAddress(“127.0.0.1”, 9999));// 创建Buffer,用于往Channel写入数据 ByteBuffer writeBuffer = ByteBuffer.wrap("hello".getBytes()); // position=0,limit=5,capacity=5 // 创建Buffer,用读取Channel中的数据 ByteBuffer readBuffer = ByteBuffer.allocate(1024); // position=0,limit=1024,capacity=1024 while (true) { // 将数据从writeBuffer中读取出来,写入到Channel中 channel.write(writeBuffer); // writeBuffer:[position=5,limit=5,capacity=5] // 切换写模式读模式,writeBuffer:[position=0,limit=5,capacity=5] writeBuffer.flip(); // 将数据从Channel中读取出来,写入到readBuffer中 channel.read(readBuffer); // readBuffer:[position=5,limit=1024,capacity=1024] // 切换写模式,readBuffer:[position=0,limit=1024,capacity=1024] readBuffer.clear(); System.out.println("Channel的数据: " + new String(readBuffer.array(), 0, readBuffer.array().length)); Thread.sleep(1000); }
}
}
### 5.3 SocketChannel与ServerSocketChannel
#### 5.3.1 Channel的简介
BIO中Socket编程的两个核心类分别为:Socket(代表客户端)和ServerSocket(代表服务器端),通过ServerSocket的accept可以接收一个客户端的Socket;
在NIO中,提供有SocketChannel和ServerSocketChannel,分别代表客户端和服务端;底层依旧采用TCP协议进行数据的网络传输,同时这些Channel还支持非阻塞方式运行,这一点与原生的Socket/ServerSocket有很大的不同;例如ServerSocketChannel在接收一个客户端时,如果还未有客户端来连接服务端,那么accept会返回null,而不是将当前线程阻塞;
#### 5.3.2 Channel的获取
通过ServerSocketChannel也可以来获取一个SocketChannel;也可以和DatagramChannel一样,通过open方法来打开一个管道;并且通过Socket可以获取SocketChannel,通过ServerSocket可以获取ServerSocketChannel;
---
和DatagramChannel一样,虽然通过Socket可以获取Channel,但该Socket必须是由Channel获取的Socket;因为原生的Socket的getChannel()方法永远返回的是null;

* 测试代码:
package com.dfbz.channel.socketChannel;
import org.junit.Test;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_Channel的获取 {
@Test
public void test1() throws Exception {
// 获取socketChannel
SocketChannel socketChannel = SocketChannel.open();
// 通过channel获取socket
Socket socket = socketChannel.socket();
// 通过socket也可以获取对应的Channel
System.out.println(socket.getChannel() == socketChannel); // true
System.out.println(socket.getClass()); // class sun.nio.ch.SocketAdaptor(并不是一个原生的Socket对象)
// serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 通过channel获取socket
ServerSocket serverSocket = serverSocketChannel.socket();
// 通过socket也可以获取对应的Channel
System.out.println(serverSocket.getChannel() == serverSocketChannel); // true
System.out.println(serverSocket.getClass()); // class sun.nio.ch.ServerSocketAdaptor(并不是一个原生的ServerSocket对象)
}
@Test
public void test2() throws Exception {
// 原生的socket并不能获取Channel
Socket socket = new Socket();
SocketChannel socketChannel = socket.getChannel();
System.out.println(socketChannel); // null