nio的通道(channel)
channel
是nio
中另一个知识,下面是网上所描述的channel
,表示IO
源与目标打开的连接,类似于传统的“流”。但是channel
不能直接访问数据,需要和缓冲区buffer
进行交互。
为什么使用Channel
上一篇文章讲过了Buffer
的创建与使用,但是对于java
程序来说,io
不只是程序内部的交互,io
更多的是说与JVM
外部数据交互,而外部数据主要有文件数据、网络数据、或者是硬件产生的一些数据。硬件又可以用File
这种文件描述符来描述。所以主要就是文件io与网络io。
假如此时需要从文件中读取内容到Buffer
中,现在不使用系统的read()
函数(操作系统对io做了很多优化处理,但是read
函数并不能很好的利用这些优化,而是使用操作系统支持的特有的、更好的读取数据的方式来实现。
为什么是特有的:在不同操作系统中,对io
读取写入的优化方式可能并不一样,所有针对不同操作系统可能需要调用不同的实现,这些实现是在java.nio.channels.spi
包中实现的,所以我们使用channel
的时候大多数是使用它们的接口。具体接口中如何实现,不需要java
编程人员关注。
为什么是更好的:更好是与FileInputStream
进行比较的,它们读取文件使用的native
方式不一样,而通道的实现方式效率更高,通道使用不同于MDA、轮询、中断这三种方式,所以效率更高。
通道简介
下面是有关Channel的的类图,转载自https://blog.csdn.net/napo_leon/article/details/24798509。
有关channel的类的直接子接口:NetWorkChannel
、AsynchronousChannel
、InterruptibleChannel
、ReadableByteChannel
、WriteableByteChannel
。
最后对通道包中类的理解,我感觉可以有两种方式进行划分,这种划分更像是知识点的总结。
第一种划分:
SelectableChannel
,可选通道,这种事非阻塞的通道可以使用,相关部分主要有一些可选相关类、可选通道类。前一部分有SelectionKey
、Selector
,后一个部分有ServerSocketChannel
、SocketChannel
、DatagramChannel
、SctpChannel
、SctpMultiChannel
、SctpServerChannel
、Pipe
等。InterruptibleChannel
,可中断通道,是一个标记接口,当被通道使用时可以标示该通道是可以中断的FileChannel
,文件通道,定义了使用通道读取文件的方式。AsynchronousChannel
,异步通道,主要引入了三个类,AsynchronousFileChannel
,AsynchronousSocketChannel
,AsynchronousServerSocketChanne
,主要用来进行异步处理- 其他,包括
GatheringByteChannel
与ScatteringByteChannel
,ReadableByteChannel
与WriteableByteChannel
,这些其实已经包括在了之前4类中,更接近于概念上。其中GatheringByteChannel
将多个buffer
中的数据写入一个channel
,ScatteringByteChannel
将多个buffer
中的数据读入一个channel
中。ReadableByteChannel
与WriteableByteChannel
:一个通道如果想既读又写,那么就需要实现这两个接口。由于channel
一般只与byte
打交道,所以ByteChannel
继承这两个接口。
另一种划分:
- 有关文件的通道
- 有关网络的通道
通道类的使用
对于通道的使用,主要用下面的例子来介绍,
FileChannel
FileChannel
类api
如下所示:
简介
可以看出FileChannel
是一个抽象类,所以创建需要使用:AccessRandomFile
、FileInputStream
、FileOutputStream
的getChannel()
方法获得,返回的都是同一个FileChannel
的实现,只不过传入的参数不一致,比如是当前获得的通道是可读、可写还是读写。
文件通道总是阻塞式的,因此不能被置于非阻塞模式。现代操作系统都有复杂的缓存和预取机制,使得本地磁盘 I/O
操作延迟很少。(有关阻塞/非阻塞、同步/异步的区别,可见其他文章)。
FileChannel
对象是线程安全的(实现中很多方法都加了synchronized
关键字),在FileChannelInpl
(需要添加openjdk
的源码才能看到实现)中,并且效率比直接使用流更好(使用了直接内存缓存,并且且读取与写入调用的native
方法与使用流也不一样)。我们以write(ByteBuffer src)
与read(ByteBuffer dst)
为例分析他们为什么效率更高:
public int write(ByteBuffer src) throws IOException {
ensureOpen();
if (!writable)
throw new NonWritableChannelException();
synchronized (positionLock) {
int n = 0;
int ti = -1;
try {
begin();
ti = threads.add();
if (!isOpen())
return 0;
do {
n = IOUtil.write(fd, src, -1, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
end(n > 0);
assert IOStatus.check(n);
}
}
}
public int read(ByteBuffer dst) throws IOException {
ensureOpen();
if (!readable)
throw new NonReadableChannelException();
synchronized (positionLock) {
int n = 0;
int ti = -1;
try {
begin();
ti = threads.add();
if (!isOpen())
return 0;
do {
n = IOUtil.read(fd, dst, -1, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
end(n > 0);
assert IOStatus.check(n);
}
}
}
实现中的读写都使用了IOUtil
工具类,如下:
static int read(FileDescriptor fd, ByteBuffer dst, long position,
NativeDispatcher nd)
throws IOException
{
if (dst.isReadOnly())
throw new IllegalArgumentException("Read-only buffer");
if (dst instanceof DirectBuffer)
return readIntoNativeBuffer(fd, dst, position, nd);
// Substitute a native buffer
ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());
try {
int n = readIntoNativeBuffer(fd, bb, position, nd);
bb.flip();
if (n > 0)
dst.put(bb);
return n;
} finally {
Util.offerFirstTemporaryDirectBuffer(bb);
}
}
static int write(FileDescriptor fd, ByteBuffer src, long position,
NativeDispatcher nd)
throws IOException
{
if (src instanceof DirectBuffer)
return writeFromNativeBuffer(fd, src, position, nd);
// Substitute a native buffer
int pos = src.position();
int lim = src.limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);
try {
bb.put(src);
bb.flip();
// Do not update src until we see how many bytes were written
src.position(pos);
int n = writeFromNativeBuffer(fd, bb, position, nd);
if (n > 0) {
// now update src
src.position(pos + n);
}
return n;
} finally {
Util.offerFirstTemporaryDirectBuffer(bb);
}
}
其中的Util.getTemporaryDirectBuffer(rem);
实现如下,可以看出申请了一块直接内存缓存区:
/**
* Returns a temporary buffer of at least the given size
*/
public static ByteBuffer getTemporaryDirectBuffer(int size) {
BufferCache cache = bufferCache.get();
ByteBuffer buf = cache.get(size);
if (buf != null) {
return buf;
} else {
// No suitable buffer in the cache so we need to allocate a new
// one. To avoid the cache growing then we remove the first
// buffer from the cache and free it.
if (!cache.isEmpty()) {
buf = cache.removeFirst();
free(buf);
}
return ByteBuffer.allocateDirect(size);
}
}
文件锁:锁的对象是文件而不是通道或线程,这意味着文件锁不适用于判优同一台 Java
虚拟机上的多个线程发起的访问。如果一个线程在某个文件上获得了一个独占锁,然后第二个线程利用一个单独打开的通道来请求该文件的独占锁,那么第二个线程的请求会被批准。但如果这两个线程运行在不同的 Java
虚拟机上,那么第二个线程会阻塞,因为锁最终是由操作系统或文件系统来判优的并且几乎总是在进程级而非线程级上判优。锁都是与一个文件关联的,而不是与单个的文件句柄或通道关联。当在以个进程中对文件同一位置加锁时,会产生错误。
使用示例
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class TestFileChannel {
public static void main(String[] args) {
try {
//系统当前的默认编码
String encoding = System.getProperty("file.encoding");
//model:rwd
RandomAccessFile randomAccessFile = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\123.txt", "rw");
FileChannel fileChannel = randomAccessFile.getChannel();
System.out.println(fileChannel.isOpen()); //true
fileChannel.close(); //关闭通道
//使用静态方法创建FileChannel,将数据写到通道中,并刷入文件
//READ,WRITE,APPEND,TRUNCATE_EXISTIN,CREATE,CREATE_NEW,DELETE_ON_CLOSE,SPARSE,SYNC,DSYNC
FileChannel fileChannel2 = FileChannel.open(Paths.get("C:\\Users\\Administrator\\Desktop\\123.txt"),StandardOpenOption.READ, StandardOpenOption.WRITE);
System.out.println(fileChannel2.isOpen()); //true
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
// byteBuffer.put("我们都是好孩子".getBytes(encoding));//
byteBuffer.put("我们都是好孩子".getBytes());//以默认编码写入
//将写buffer转换为读buffer
byteBuffer.flip();
//将buffer中的内容写入fileChannel2中
fileChannel2.write(byteBuffer);
//将byteBuffer置为空,写buffer
byteBuffer.clear();
System.out.println(byteBuffer);//java.nio.HeapByteBuffer[pos=0 lim=100 cap=100]
System.out.println(byteBuffer.asCharBuffer());//此时是乱码,写入文件时以系统默认编码写入,但是读出时将两个byte作为一个字符
//将通道中内容写入文件,如果文件存储再本地,那么可以保证内容一定会被写入文件,如果不在本地,则无法保证。详情看注释
fileChannel2.force(true);
fileChannel2.close();
//将数据从文件中读出
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\123.txt");
FileChannel fileChannel3 = fileInputStream.getChannel();
fileChannel3.read(byteBuffer);
byteBuffer.flip();
System.out.println(byteBuffer.asCharBuffer());//乱码,写入文件时以系统默认编码写入,但是读出时将两个byte作为一个字符
//准备重读
byteBuffer.rewind();
//使用系统默认的编码格式进行解码
System.out.println(Charset.forName(encoding).decode(byteBuffer));//我们都是好孩子
fileChannel3.close();
//其他函数
FileInputStream fileInputStream1 = new FileInputStream("C:\\Users\\Administrator\\Desktop\\123.txt");
FileOutputStream fileOutputStream1 = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\1234.txt");
FileChannel fisc = fileInputStream1.getChannel();
FileChannel fosc = fileOutputStream1.getChannel();
//read与write函数参数为Buffer数组的,表示使用GatheringByteChannel与ScatteringByteChannel相类似效果
System.out.println(fisc.size()); //14
//件fisc通道中的数据传输到fosc通道中,但是"并不保证所有的数据都被传输",尤其是“写通道为非阻塞”的时候容易出问题,
//这种方式可以用来拷贝文件,效率比较高
fisc.transferTo(0, fisc.size(), fosc);
// fisc.transferFrom(fisc, 0, fisc.size());
fosc.truncate(10);//缩短文件大小,详细变化看源码注释.文件内容变成了“我们都是好”
fisc.close();
fosc.close();
//内存映射文件:
RandomAccessFile randomAccessFile1 = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\123.txt", "rw");
FileChannel fileChannel4 = randomAccessFile1.getChannel();
//PRIVATE/READ_ONLY/READ_WRITE
//对于MappedByteBuffer的使用在前一篇中介绍
MappedByteBuffer mappedByteBuffer = fileChannel4.map(MapMode.PRIVATE, 0, 1024);
fileChannel4.close();
//文件锁问题
RandomAccessFile randomAccessFile2 = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\123.txt", "rw");
FileChannel fileChannel5 = randomAccessFile2.getChannel();
System.out.println(fileChannel5.tryLock(0,2, true));
//fileChannel5.tryLock(position, size, shared);
//fileChannel5.lock();//报错,加锁的位置重合。因为加锁是对文件的,而不是文件描述符,所以当同一个进程对相同的文件或者文件位置加锁时,会报错
FileLock fileLock = fileChannel5.lock(2,4,false); //不报错,加锁与之前加锁的位置没有交集。
System.out.println(fileLock.isShared()); //false
fileLock.release();
fileChannel5.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
对于这些文件的操作,最好在在finally
中关闭通道。如果想要检查没有关闭的文件引用,可以按照基础篇中的描述查找。
ScatteringByteChannel 与 GatheringByteChannel
简介
scatter
散布,gather
聚集。scatter
表示将一个通道中的数据读取并散布到多个数据缓冲区,这是一个read
操作;gather
表示将多个数据缓冲区的数据聚集起来并沿着通道发送,这是一个write
操作。
那么如果是scatter
,那么每个数据缓冲区分多少?从通道读取的数据会按顺序被散布到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间被消耗完。也就是从第一个缓冲区开始填充,填充完第一个填充第二个,依次直到通道数据没有,或者所有缓冲区都被填满。
gather
就是从第一个缓冲区开始读取,然后第二个、第三个,直到缓冲区都读完。
这个功能有时候也称为矢量IO,大多数现代操作系统都支持本地矢量 I/O(native vectored I/O)
。当您在一个通道上请求一个Scatter/Gather
操作时,该请求会被翻译为适当的本地调用来直接填充或抽取缓冲区。这是一个很大的进步,因为减少或避免了缓冲区拷贝和系统调用。Scatter/Gather
应该使用直接的 ByteBuffers
以从本地 I/O 获取最大性能优势。
在我这个jdk版本中,主要有以下的实现,有关UDP
、FIle
、TCP
、Pipe
的四个实现。
接口API定义如下:
实现类定义如下:
查看FileChannelImpl
的write
方法实现如下:
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException
{
if ((offset < 0) || (length < 0) || (offset > srcs.length - length))
throw new IndexOutOfBoundsException();
ensureOpen();
if (!writable)
throw new NonWritableChannelException();
synchronized (positionLock) {
long n = 0;
int ti = -1;
try {
begin();
ti = threads.add();
if (!isOpen())
return 0;
do {
//写操作
n = IOUtil.write(fd, srcs, offset, length, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
end(n > 0);
assert IOStatus.check(n);
}
}
}
其中的IOUtil.write(fd, srcs, offset, length, nd);
实现的代码如下:
static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
NativeDispatcher nd)
throws IOException
{
IOVecWrapper vec = IOVecWrapper.get(length);
boolean completed = false;
int iov_len = 0;
try {
// Iterate over buffers to populate native iovec array.
int count = offset + length;
int i = offset;
while (i < count && iov_len < IOV_MAX) {
ByteBuffer buf = bufs[i];
int pos = buf.position();
int lim = buf.limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (rem > 0) {
vec.setBuffer(iov_len, buf, pos, rem);
// allocate shadow buffer to ensure I/O is done with direct buffer
if (!(buf instanceof DirectBuffer)) {
ByteBuffer shadow = Util.getTemporaryDirectBuffer(rem);
shadow.put(buf);
shadow.flip();
vec.setShadow(iov_len, shadow);
buf.position(pos); // temporarily restore position in user buffer
buf = shadow;
pos = shadow.position();
}
vec.putBase(iov_len, ((DirectBuffer)buf).address() + pos);
vec.putLen(iov_len, rem);
iov_len++;
}
i++;
}
if (iov_len == 0)
return 0L;
long bytesWritten = nd.writev(fd, vec.address, iov_len);
// Notify the buffers how many bytes were taken
long left = bytesWritten;
for (int j=0; j<iov_len; j++) {
if (left > 0) {
ByteBuffer buf = vec.getBuffer(j);
int pos = vec.getPosition(j);
int rem = vec.getRemaining(j);
int n = (left > rem) ? rem : (int)left;
buf.position(pos + n);
left -= n;
}
// return shadow buffers to buffer pool
ByteBuffer shadow = vec.getShadow(j);
if (shadow != null)
Util.offerLastTemporaryDirectBuffer(shadow);
vec.clearRefs(j);
}
completed = true;
return bytesWritten;
} finally {
// if an error occurred then clear refs to buffers and return any shadow
// buffers to cache
if (!completed) {
for (int j=0; j<iov_len; j++) {
ByteBuffer shadow = vec.getShadow(j);
if (shadow != null)
Util.offerLastTemporaryDirectBuffer(shadow);
vec.clearRefs(j);
}
}
}
}
writev
实现如下:
long writev(FileDescriptor fd, long address, int len)
throws IOException
{
//调用本地方法
return writev0(fd, address, len);
}
抄了这么长的代码,也没有仔细去研究了一下。
代码示例
下面的例子是《java nio》书中的示例:
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.GatheringByteChannel;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
public class TestGatherByteChannel {
private static final String DEMOGRAPHIC = "C:\\Users\\Administrator\\Desktop\\1234.txt";
// "Leverage frictionless methodologies"
public static void main (String [] argv) throws Exception {
int reps = 10;
if (argv.length > 0) {
reps = Integer.parseInt (argv [0]);
}
FileOutputStream fos = new FileOutputStream (DEMOGRAPHIC);
GatheringByteChannel gatherChannel = fos.getChannel( );
// Generate some brilliant marcom, er, repurposed content
ByteBuffer [] bs = utterBS (reps);
// Deliver the message to the waiting market
while (gatherChannel.write (bs) > 0) {
// Empty body
// Loop until write( ) returns zero
}
System.out.println ("Mindshare paradigms synergized to "
+ DEMOGRAPHIC);
fos.close( );
}
// ------------------------------------------------
// These are just representative; add your own
private static String [] col1 = {
"Aggregate", "Enable", "Leverage",
"Facilitate", "Synergize", "Repurpose",
"Strategize", "Reinvent", "Harness"
};
private static String [] col2 = {
"cross-platform", "best-of-breed", "frictionless",
"ubiquitous", "extensible", "compelling",
"mission-critical", "collaborative", "integrated"
};
private static String [] col3 = {
"methodologies", "infomediaries", "platforms",
"schemas", "mindshare", "paradigms",
"functionalities", "web services", "infrastructures"
};
private static String newline = System.getProperty ("line.separator");
// The Marcom-atic 9000
private static ByteBuffer [] utterBS (int howMany) throws Exception {
List<ByteBuffer> list = new LinkedList<ByteBuffer>( );
for (int i = 0; i < howMany; i++) {
list.add (pickRandom (col1, " "));
list.add (pickRandom (col2, " "));
list.add (pickRandom (col3, newline));
}
ByteBuffer [] bufs = new ByteBuffer [list.size( )];
list.toArray (bufs);
return (bufs);
}
// The communications director
private static Random rand = new Random( );
// Pick one, make a buffer to hold it and the suffix, load it with
// the byte equivalent of the strings (will not work properly for
// non-Latin characters), then flip the loaded buffer so it's ready
// to be drained
private static ByteBuffer pickRandom (String [] strings, String suffix) throws Exception {
String string = strings [rand.nextInt (strings.length)];
int total = string.length() + suffix.length( );
ByteBuffer buf = ByteBuffer.allocate (total);
buf.put (string.getBytes ("US-ASCII"));
buf.put (suffix.getBytes ("US-ASCII"));
buf.flip( );
return (buf);
}
}
可选通道
见下一篇SelectableChannel
异步通道
有关异步通道感觉写的比较好的:
https://www.ibm.com/developerworks/cn/java/j-lo-nio2/index.html
异步通道AsynchronousChannel
是在jdk1.7中添加的,异步原意为完成读写操作后通知程序操作完成。其主要实现类如下,分为有关网络、有关文件的:
在这些类中,对异步操作提供两种方式,其实这两种方式大致相同,都是创建了线程,不同的是返回Future
,在主线程中操作;而将CompletionHandle
作为对象传入是在创建的线程中执行的。但是这并不是所说的异步通道,此处的异步说的是其中对IO的操作,涉及到底层的IO是异步,并不是线程上的异步。
- 返回一个
Future
对象,可以使用该对象的isDone
与get
函数进行操作 - 使用一个
CompletionHandle
对象作为参数传入,操作完成后回调CompletionHandler
对象的方法
AsynchronousFileChannel
使用AsynchronousFileChannel
最后生成一个WindowsAsynchronousFileChannelImpl
的实例,而在AsynchronousFileChannel
抽象类中定义了对文件的锁、异步操作等。其API函数如下:
以下示例中使用open
的方式,去将异步通道绑定到文件上。
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class TestAsynchronousFileChannel {
volatile ByteBuffer dst = ByteBuffer.allocate(50000);
public void test() throws IOException, InterruptedException, ExecutionException {
Path file = Paths.get("C:\\Users\\Administrator\\Desktop\\1234.txt") ;
AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel.open(file, StandardOpenOption.READ);
Future<Integer> future = asynchronousFileChannel.read(dst , 20);
System.out.println(future.get());
dst.flip();
System.out.println(dst);
dst.clear();
System.out.println("--------------");
asynchronousFileChannel.read(dst, 0, dst, new CompletionHandler<Integer, ByteBuffer>() {
public void completed(Integer result, ByteBuffer attachment) {
System.out.println(result);
System.out.println(attachment == dst);
dst.flip();
//比较郁闷的是,我在这里如果输出("传入的对象:" + new String(attachment.array())),经常会什么都输不出
System.out.println("传入的对象:" + attachment);
}
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println(exc);
}
});
asynchronousFileChannel.close();
}
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
TestAsynchronousFileChannel asynchronousFileChannel = new TestAsynchronousFileChannel();
asynchronousFileChannel.test();
}
}
AsynchronousSocketChannel与AsynchronousServerSocketChannel
这二者与SocketChannel
、ServerSocketChannel
类似,只是SocketChannel
提供的read
、write
方法变为异步实现,而ServerSocketChannel
提供的accept
方法也变为异步实现。
示例代码如下,代码来自https://www.ibm.com/developerworks/cn/java/j-nio2-1/:
服务端:
import java.io.IOException;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class AsynchronousChannelGroupExample {
public static void main(String[] args) throws IOException, InterruptedException {
new AsynchronousChannelGroupExample();
}
public AsynchronousChannelGroupExample() throws IOException, InterruptedException {
// create a channel group
AsynchronousChannelGroup tenThreadGroup = AsynchronousChannelGroup.withFixedThreadPool(10, Executors.defaultThreadFactory());
// and pass to a channel to use
System.out.print("Create a channel with a channel group");
AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open(tenThreadGroup).bind(null);
// now initiate a call that won't be satisfied
System.out.println("and start an accept that won't be satisfied");
channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>(){
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
}
@Override
public void failed(Throwable exc, Object attachment) {
}});
if (!tenThreadGroup.isShutdown()) {
System.out.println("Shutdown channel group");
// mark as shutdown, no more channels can now be created with this pool
tenThreadGroup.shutdown();
}
if (!tenThreadGroup.isTerminated()) {
System.out.println("Terminate channel group");
// forcibly shutdown, the channel will be closed and the read will abort
tenThreadGroup.shutdownNow();
}
System.out.println("Wait for termination");
// the group should be able to terminate now, wait for a maximum of 10 seconds
boolean terminated = tenThreadGroup.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("Group is terminated? " + terminated);
}
}
客户端:
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class AsynchronousSocketChannelExample {
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
new AsynchronousSocketChannelExample();
}
public AsynchronousSocketChannelExample() throws IOException, InterruptedException, ExecutionException {
// open a server channel and bind to a free address, then accept a connection
System.out.println("Open server channel");
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(null);
System.out.println("Initiate accept");
Future<AsynchronousSocketChannel> future = server.accept();
// create a client
Client client = new Client(server.getLocalAddress());
// wait for the accept to finish
AsynchronousSocketChannel worker = future.get();
System.out.println("Accept completed");
// start client thread
client.start();
ByteBuffer readBuffer = ByteBuffer.allocate(100);
try {
// read a message from the client, timeout after 10 seconds
worker.read(readBuffer).get(10, TimeUnit.SECONDS);
System.out.println("Message received from client: " + new String(readBuffer.array()));
} catch (TimeoutException e) {
System.out.println("Client didn't respond in time");
}
client.join();
client.close();
server.close();
}
}
class Client extends Thread {
AsynchronousSocketChannel client;
Future<Void> connectFuture;
public Client(SocketAddress server) throws IOException {
// open a new socket channel and connect to the server
System.out.println("Open client channel");
client = AsynchronousSocketChannel.open();
System.out.println("Connect to server");
connectFuture = client.connect(server);
}
public void run() {
// if the connect hasn't happened yet cancel it
if (!connectFuture.isDone()) {
connectFuture.cancel(true);
return;
}
try {
// send a message to the server
ByteBuffer message = ByteBuffer.wrap("ping".getBytes());
// wait for the response
System.out.println("Sending message to the server...");
int numberBytes = client.write(message).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public void close() throws IOException {
client.close();
}
}
可中断通道
http://jnullpointer.iteye.com/blog/2119982
InterruptibleChannel。
package java.nio.channels;
import java.io.IOException;
/**
* A channel that can be asynchronously closed and interrupted.
* 可以被异步地关闭或者中断的通道
*
* 实现这个接口的通道可以被异步关闭:如果线程由于一个可中断的通道的IO操作阻塞,此时另一个线程区去关闭这个通道,会导致被阻塞的线程收到一个AsynchronousCloseException异常
* <p> A channel that implements this interface is <i>asynchronously
* closeable:</i> If a thread is blocked in an I/O operation on an
* interruptible channel then another thread may invoke the channel's {@link
* #close close} method. This will cause the blocked thread to receive an
* {@link AsynchronousCloseException}.
*
* 实现这个接口的通道可以被中断,如果线程由于一个可中断的通道的IO操作阻塞,此时另一个线程执行被阻塞线程的interrupt()方法,被阻塞的线程收到一个ClosedByInterruptException异常,并且线程的interrupt status被设置。如果interrupt status之前已经被设置了,那么此时会直接关闭通道
* <p> A channel that implements this interface is also <i>interruptible:</i>
* If a thread is blocked in an I/O operation on an interruptible channel then
* another thread may invoke the blocked thread's {@link Thread#interrupt()
* interrupt} method. This will cause the channel to be closed, the blocked
* thread to receive a {@link ClosedByInterruptException}, and the blocked
* thread's interrupt status to be set.
*
* <p> If a thread's interrupt status is already set and it invokes a blocking
* I/O operation upon a channel then the channel will be closed and the thread
* will immediately receive a {@link ClosedByInterruptException}; its interrupt
* status will remain set.
*
* <p> A channel supports asynchronous closing and interruption if, and only
* if, it implements this interface. This can be tested at runtime, if
* necessary, via the <tt>instanceof</tt> operator.
*
*
* @author Mark Reinhold
* @author JSR-51 Expert Group
* @since 1.4
*/
public interface InterruptibleChannel
extends Channel
{
/**
* Closes this channel.
*
* <p> Any thread currently blocked in an I/O operation upon this channel
* will receive an {@link AsynchronousCloseException}.
*
* <p> This method otherwise behaves exactly as specified by the {@link
* Channel#close Channel} interface. </p>
*
* @throws IOException If an I/O error occurs
*/
public void close() throws IOException;
}
其他知识
InetAddress
这是一个描述IP(Internet Protocal
)地址的类,一个IP地址可能是32bit或128bit的无符号数字。所以InetAddress
有两个子类,即ipv4、ipv6两个版本。
下面的描述来自:https://blog.csdn.net/u012561176/article/details/48183181
InetAddress
的实例对象包含以数字形式保存的IP地址,同时还可能包含主机名(如果使用主机名来获取InetAddress
的实例,或者使用数字来构造,并且启用了反向主机名解析的功能)。InetAddress
类提供了将主机名解析为IP地址(或反之)的方法。
InetAddress
对域名进行解析是使用本地机器配置或者网络命名服务(如域名系统(Domain Name System
,DNS
)和网络信息服务(Network Information Service
,NIS
))来实现。对于DNS
来说,本地需要向DNS
服务器发送查询的请求,然后服务器根据一系列的操作,返回对应的IP地址,为了提高效率,通常本地会缓存一些主机名与IP地址的映射,这样访问相同的地址,就不需要重复发送DNS
请求了。在java.net.InetAddress
类同样采用了这种策略。在默认情况下,会缓存一段有限时间的映射,对于主机名解析不成功的结果,会缓存非常短的时间(10秒)来提高性能。
主要使用示例:
import java.net.InetAddress;
import java.net.UnknownHostException;
public class TestInetAddress {
public static void main(String[] args) throws UnknownHostException {
InetAddress inetAddress1 = InetAddress.getLocalHost();
//getHostAddress返回的是ip格式,getAddress返回的是byte[]
System.out.println("HostName:" + inetAddress1.getHostName() + ",ipAddress:" + inetAddress1.getHostAddress());
System.out.println("-----------------");
//想使用ip地址获得InertAddress,可以使用getByName。如果使用.getByAddress(addr)则比较麻烦,因为addr是一个4字节长的东西,需要转换
InetAddress inetAddress2 = InetAddress.getByName("192.168.135.1");
//这个ip是我本地的ip地址
System.out.println("HostName:" + inetAddress2.getHostName() + ",ipAddress:" + inetAddress2.getHostAddress());
System.out.println("-----------------");
InetAddress inetAddress3 = InetAddress.getByName("127.0.0.1");
System.out.println("HostName:" + inetAddress3.getHostName() + ",ipAddress:" + inetAddress3.getHostAddress());
System.out.println("-----------------");
InetAddress[] inetAddresses = InetAddress.getAllByName("www.baidu.com");
int i = 0;
for(InetAddress address :inetAddresses) {
System.out.println("ipAddress"+ (i++) +":" + address.getHostAddress());
}
}
}