NIO , BIO的实现与区别
- BIO方式:同步并阻塞,一个连接一个线程,适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
- NIO方式:同步非阻塞,一个请求一个线程,适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
- AIO方式:异步非阻塞,一个有效连接一个线程,适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
同步:java自己处理IO读写
异步:java委托给操作系统处理IO操作
阻塞:只能等待完成
非阻塞:可以去干别的,好了会有通知的
NIO由三个主要的部分组成:
缓冲区(Buffers)、通道(Channels)和非阻塞I/O的核心类组成。
NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的高并发问题:在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。
如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。
NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。
也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
IO包
主要用途:文件,网络,内存缓存,线程内部通信(管道),缓冲,过滤,解析,读写文本,读写基本类型,读写对象。
核心部分:
- Channel: 该对象模拟了通道连接,管道可以是单向的,也可以是双向的。
- Buffer: 是Java类与Channels之间的纽带。Channels可以提取存放在Buffer中的数据(写),也可以存入数据供读取(取)
- Selector: 提供了确定一个或多个通道当前状态的机制。使用选择器,借助单一线程,可对数量庞大的活动IO通道实施监控和维护。
Channel和Buffer: Channel可对Buffer进行读写
Channel和Selector: 单线程处理多个Channel.
Java NIO-Buffer
属性:
- Capacity: 容量,缓存区能够容纳的数据元素的最大数量,在缓冲区创建时设定,并且永远不能改变
- Limit: 上界,缓存区中已使用的数据量
- Position: 位置,下一个要被读或写的元素(如byte)的索引
- Mark: 标记,做一个书签,便于操作
创建Buffer:
//使用allocate静态方法
ByteBuffer buffer = ByteBuffer.allocate(10);//容量为100
//使用wrap静态方法
char[] myChars = new char[10];
CharBuffer buffer = CharBuffer.wrap(myChars);//将myChars数组作为内部封装的缓存区
//指定position=3,limit=8的缓存区
char[] myChars = new char[10];
CharBuffer buffer = CharBuffer.wrap(myChars, 3, 8);//将myChars数组作为内部封装的缓存区
//CharBuffer独有的函数:
//public static CharBuffer CharBuffer.wrap(CharSequence csq,int start.int end);
CharBuffer b = CharBuffer.wrap("Hello World");//这样创建的Buffer是只读的...
public final boolean hasArray()
public final char[] array()
public final int arrayOffset()//返回缓存区数据在数组中存储的开始位置的偏移量
操作Buffer:
- 存取
ByteBuffer buff = ByteBuffer.allocate(10);
//get() => get(int index)
buff.put((byte) 'A');
buff.put((byte) 'B');
buff.put((byte) 'C');
buff.put((byte) 'D');
- 翻转:可用于将写入状态转换为读取状态
buff.flip() //=> buff.limit(buff.position()).position(0)
//buff.rewind() => 仅将position设为0
- 读取
//put(byte b) => put(int index, byte b)
System.out.println((char)buff.get());
System.out.println((char)buff.get());
//hasRemaining()和remaining()
for (int i = 0; buff.hasRemaining(); i++){
System.out.print(buff.get());
}
int count = buff.remaining();
for (int j = 0; j < count; j++){
System.out.print(buff.get());
}
- 压缩
compact():将position之后的元素复制到缓存区首部,其余位置的元素不变
// 创建一个容量为10的byte数据缓冲区
ByteBuffer buff = ByteBuffer.allocate(10);
// 填充缓冲区
buff.put((byte)'A');
buff.put((byte)'B');
buff.put((byte)'C');
buff.put((byte)'D');
System.out.println("first put : " + new String(buff.array()));
//翻转
buff.flip();
//释放
System.out.println((char)buff.get());
System.out.println((char)buff.get());
//压缩
buff.compact();
System.out.println("compact after get : " + new String(buff.array()));
//继续填充
buff.put((byte)'E');
buff.put((byte)'F');
//输出所有
System.out.println("put after compact : " + new String(buff.array()));
/*
first put : ABCD
A
B
compact after get : CDCD //将原先2,3位置的字节复制到0,1位置。原位置字节不变
put after compact : CDEF //并将position指向复制位置的下一个位置
*/
- 标记
remark():标记就是记住当前位置(使用mark()方法标记),之后可以将位置恢复到标记处(使用reset()方法恢复)
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
- 比较
equals(Object obj):比较两个缓存区的每个值, 可以是比较不同的缓存区(返回false)
compareTo(T that):比较相同类型的缓存区,比较position到limit之间的缓存值
- >批量移动:高效移动数据,这是缓存区的目的
//批量取出数据
public CharBuffer get(char[] dst)
public CharBuffer get(char[] dst,int offset,int length)
//批量存入数据
public final CharBuffer put(char[] src)
public final CharBuffer put(char[] src, int offset,int length)
public final CharBuffer put(CharBuffer src)
public final CharBuffer put(String src)
public final CharBuffer put(String src,int start,int end)
复制缓存区
public abstract CharBuffer duplicate(); //复制为一个可读可写的缓存区
public abstract CharBuffer asReadOnlyBuffer(); //复制为一个只读缓冲区
public abstract CharBuffer slice(); //复制一个从源缓冲position到limit的新缓冲区
ByteBuffer => IO操作等核心
Java NIO-ByteBuffer
- 大端字节顺序和小端字节顺序
IP协议规定了使用大端的网络字节顺序,所有在IP分组报文的协议部分中使用的多字节数值必须现在本地主机字节顺序和通用的网络字节顺序之间进行转换。
public final class ByteOrder{
public static final ByteOrder BIG_ENDIAN; //大端
public static final ByteOrder LITTLE_ENDIAN; //小端
public static ByteOrder nativeOrder() //返回运行JVM机器的字节顺序
}
public abstract class CharBuffer extends Buffer ...{
public final ByteOrder order(); //返回运行JVM机器的字节顺序
}
- 直接缓存区
只有ByteBuffer才能参与IO操作,但操作系统的IO操作的目标内存区域必须是连续的字节序列,而byte[]在JVM中的实现并不是连续的字节。所以引入了直接缓存区的概念。
直接缓存区被用于通道与固有的IO线程交互
public static ByteBuffer allocateDirect(int capacity) //分配直接缓存区
public abstract boolean isDirect()
- 视图缓存区
例:如将ByteBuffer转换为CharBuffer, 每2个字节转换为1个char
asCharBuffer()
asShortBuffer()
asIntBuffer()
asLongBuffer()
asFloatBuffer()
asDoubleBuffer()
ByteBuffer byteBuffer = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
CharBuffer charBuffer = byteBuffer.asCharBuffer()
//将2个字节转换为1个字符,UTF-16编码刚刚好
byte[] bytes = "Hello".getBytes(Charset.forName("UTF-16"));
ByteBuffer b = ByteBuffer.wrap(bytes);
CharBuffer c = b.asCharBuffer();
System.out.println(c);
- 数据元素视图
ByteBuffer buff = ByteBuffer.allocate(100);
buff.putShort((short)100);
buff.putInt(200);
buff.putLong(300L);
buff.putFloat(400.5f);
buff.putDouble(500.56);
buff.putChar('A');
buff.flip();
buff.getShort();//return 100
buff.getInt();//return 200
buff.getLong();//return 300
buff.getFloat();//return 400.5
buff.getDouble();//return 500.56
buff.getChar();//return 'A'
Java NIO-Channel
- 打开通道
//文件通道:只能通过RandomAccessFile, FileInputStream, FileOutputStream
RandomAccessFile file = new RandomAccessFile("somefile","r")
FileChannel fc = file.getChannel();
//Socket通道
SocketChannel sc = SocketChannel.open()
sc.connect(new InetSocket("119.28.83.71",2222));
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocket("119.28.83.71",2222));
- 使用通道和关闭通道
//注意区分只读文件和可读写文件
FileInputStream input = new FileInputStream("filename");
FileChannel channel = input.getChannel();
channel.write(buffer); //InputStrem只能读,不能写
channel.read(buffer); //将文件内容读入channel
public class Test {
public static void main(String...args) throws IOException{
ReadableByteChannel source = Channels.newChannel(System.in);
WritableByteChannel destin = Channels.newChannel(System.out);
channelCopy2(source,destin);
source.close();
destin.close();
}
private static void channelCopy2(ReadableByteChannel src,
WritableByteChannel des) throws IOException{
ByteBuffer buffer = ByteBuffer.allocateDirect(10);
while (src.read(buffer) != -1){ //将输入的内容读取到buffer
buffer.flip(); //切换为写状态
while (buffer.hasRemaining()){ //比如只用了5个字节
des.write(buffer); //那就只写5个字节
}
buffer.clear(); //清空缓存区
}
}
}
调用通道的close()方法,可能会导致在通道关闭底层IO服务中,似的线程暂时阻塞。
可以通过isOpen()来查看通道是否开着。当一个通道实现了InterruptibleChannel接口,当对被阻塞的通道调用interrupt()方法时,通道也会自动关闭。
- Scatter/Gather
- 文件通道
FileChannel | RandomAccessFile | POSIX system call |
---|---|---|
read() | read() | read() |
write() | write() | write() |
size() | length() | fstat(): 成功返回0,失败返回1 |
position():表示文件中当前字节位置 | getFilePointer():返回当前偏移 | lseek() |
position(long newPosition):设置位置 | seek():设置文件偏移 | lseek() |
truncate(long size):删减到指定大小 | setLength() | ftruncate() |
force(boolean metaData):参数不管,这个方法的作用相当于flush() | getFD().sync() | fsync() |
* 文件锁定
//独占锁,锁定全部文件
public final FileLock lock();
//开始位置,和锁定区域的大小,以及是共享锁和独占锁
public abstract FileLock lock(long position, long size, boolean shared);
//不能立即获取,则返回null
public final FileLock tryLock();
//开始位置,和锁定区域的大小,以及是共享锁和独占锁 => 尝试获取
public abstract FileLock tryLock(long position, long size, boolean shared);
FileLock
public abstract class FileLock{
public final FileChannel channel(); //返回创建锁的Channel
public final long position(); //锁定的起始位置
public final long size(); //锁定的大小
public final boolean isShared(); //判断是独占还是共享锁
public final boolean overlaps(long position, long size); //是否与一个指定的区域有重叠
public abstract boolean isValid(); //锁的有效性
public abstract void release() throws IOException; //释放锁
}
- 内存映射文件
调用map()会创建一个由磁盘文件支持的虚拟内存映射,并在那块虚拟内存空间外部封装一个MappedByteBuffer对象。
ByteBuffer是在内存中建立缓存,而MappedByteBuffer是在磁盘文件建立一个缓存区。
public abstract class FileChannel ...{
public abstract MappedByteBuffer map(MapMode mode, long position, long size);
public static class MapMode{
public static final MapMode READ_ONLY //只可读
= new MapMode("READ_ONLY");
public static final MapMode READ_WRITE //可读写
= new MapMode("READ_WRITE");
public static final MapMode PRIVATE //写时拷贝,不会对底层文件做任何修改
= new MapMode("PRIVATE");
}
}
Channel-to-channel传输
public abstract class FileChannel ...{
public abstract long transferTo(long position, long count, WriteableByteChannel target);
public abstract long transferFrom(ReadableByteChannel src, long position, long count);
}
Java NIO-SocketChannel
- 配置堵塞模式
定义于SelectableChannel类里面
SocketChannel sc = SocketChannel.open() //打开通道
sc.configureBlocking(false); //配置非堵塞模式
if(!sc.isBlocking()){ //如果是非堵塞模式
...
}
Socket
Object lockObj = sc.blockingLock() //获取堵塞锁,为了防止堵塞模式被修改
synchronized(lockObj){ //加锁
boolean prevState = sc.isBlocking;
sc.configureBlocking(false);
socket = sc.accept()
sc.configureBlocking(prevState);
}
if (socket != null){
doSomething(socket);
}
- ServerSocketChannel
public abstract class ServerSocketChannel extends AbstractSelectableChannel{
public static ServerSocketChannel open();
public abstract ServerSocket socket();
public abstract ServerSocket accept();
public final int validOps();
}
//绑定端口
ServerSocketChannel ssc = ServerSocketChannel.open() //打开ServerSocketChannel
ServerSocket socket = ssc.socket() //打开Socket
socket.bind(new InetSocketAddress(1234)) //绑定端口
//Echo TCP 服务器
public class Test {
public static final String GREETING = "Hello i must be going";
public static void main(String...args) throws Exception {
int port = 1234;
ByteBuffer buffer = ByteBuffer.wrap(GREETING.getBytes());
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().setReuseAddress(true);
ssc.socket().bind(new InetSocketAddress(port));
ssc.configureBlocking(false);
while (true){
System.out.println("Waiting for connections");
SocketChannel sc = ssc.accept();
if (sc == null){
Thread.sleep(2000);
}else {
System.out.println("Incoming connection from:" + sc.socket().getRemoteSocketAddress());
buffer.rewind();
sc.write(buffer);
sc.close();
}
}
}
}
- SocketChannel
public abstract class SocketChannel extends AbstractSelectableChannel{
public static SocketChannel open(); //仅打开ServerSocket
public static SocketChannel open(InetSocketAddress remote); //打开并连接到远程服务器
public abstract Socket socket();
public abstract boolean connect(SocketAddress remote)
public abstract boolean isConnectionPending(); //连接正在建立?
public abstract boolean finishConnect(); //完成连接了?
public abstract boolean isConnected(); //连接是否建立
public final int validOps();
}
connect()的堵塞和非堵塞:如果在非堵塞模式下,如果立即连接了会返回true,而没有立即连接会返回false并且并发地继续建立连接。通过isConnectPending()来查看连接是否在进行连接。
finishConnect()任何时刻都能安全的调用:
- connect()为调用,产生NoConnectionPendingException
- 建立正在进行,返回false
- 在非堵塞模式调用connect(), 但又切换成堵塞模式,线程会阻塞,直到建立完成,返回true
- 连接已经连接,返回true
public class Test {
public static void main(String...args) throws Exception {
String host = "www.baidu.com";
int port = 80;
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
System.out.println("Begin to connecting");
sc.connect(new InetSocketAddress(host,port));
while (!sc.finishConnect()){
System.out.println("Connecting...");
Thread.sleep(100);
}
System.out.println("Connection finish");
sc.close();
}
}
- DatagramChannel:
public abstract class DategramChannel extends AbstractSelectableChannel ...{
public static DatagramChannel open();
public abstract DatagramSocket socket();
public abstract DatagramChannel connect(SocketAddress remote);
public abstract boolean isConnected();
public abstract DatagramChannel disconnect();
public abstract SocketAddress receive(ByteBuffer dst); //接受服务器的数据,写入Buffer,返回服务器地址
public abstract int send(ByteBuffer src,SocketAddress target); //向服务器发送数据,无需连接
public abstract int read(ByteBuffer dst);
public abstract int write(ByteBuffer src);
}
两种建立客户端的方法
//方法一:使用send()和receive()
ByteBuffer buffer = ByteBuffer.wrap("Hello World".getBytes());
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(true);
channel.send(buffer,new InetSocketAddress("localhost",1235));
buffer.rewind();
channel.receive(buffer);
channel.close();
System.out.print(new String(buffer.array()));
//方法二:使用read()和write()
ByteBuffer buffer = ByteBuffer.wrap("Hello World".getBytes());
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(true);
channel.connect(new InetSocketAddress("localhost",1235));
if (channel.isConnected()){
channel.write(buffer);
}
buffer.rewind();
channel.read(buffer);
channel.close();
System.out.print(new String(buffer.array()));
快速的建立服务器
//看看,根本没有connect
ByteBuffer buffer = ByteBuffer.allocate(20);
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(true);
channel.bind(new InetSocketAddress("localhost",1235));
while (true){
SocketAddress addr = channel.receive(buffer);
channel.send(buffer,addr);
}
- Channels工具类
Java NIO-Selector
三个核心类:
- Selector: 管理着一个被注册的通道集合的Set<SelectionKey>的信息和他们的就绪状态。
- SelectableChannel: 包括Socket等的通道,可以被注册到Selector对象上。一个通道可以注册到多个Selector上
- SelectionKey: 封装了特定的Channel和Selector之间的注册关系。该对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。
建立选择器
Selector selector = Selector.open();
channel1.register(selector, SelectionKey.OP_READ);
channel2.register(selector, SelectionKey.OP_WRITE);
channel3.register(selector, SelectionKey.OP_READ | SelctorKey.OP_WRITE);
//让当前线程等待1000,然后返回有几个线程已经准备好了
int readyCount = selector.select(1000);
详解
public abstract class Selector{
public static Selector open() throws IOException; //向SPI发出请求,通过默认的SelectorProvider创建
public abstract boolean isOpen(); //查看Selector是否打开
public abstract void close() throws IOException; //关闭一个Selector
public abstract SelectorProvider provider(); //返回创建自己的SelectorProvider对象
public abstract Set keys(); //返回所有键
public abstract Set selectedKeys(); //返回通道已准备的键
public abstract int select(); //等待直到有准备好的通道
public abstract int select(long timeout) //等待timeout的时间
public abstract int selectNow() //非堵塞,如果没有准备好的,就返回0
public abstract void wakeup(); //使得选择器上的第一个还没有返回的选择器立即返回
public abstract void close() //使得selector堵塞的线程立即返回,取消注册的Channel
}
public abstract class SelectorChannel ...{
public abstract SelectionKey register(Selector sel, int ops);
public abstract SelectionKey register(Selector sel, int ops, Object att);//att是一个属性,可以通过attr获取
public abstract boolean isRegistered();
public abstract SelectionKey keyFor(Selector sel);
public abstract int validOps();
}
public abstract class SelectionKey{
public static final int OP_READ; //1 << 0;®®
public static final int OP_WRITE; //1 << 2;
public static final int OP_CONNECT; //1 << 3;
public static final int OP_ACCEPT; //1 << 4;
public abstract SelectableChannel channel(); //返回与该键相关的SelectorChannel
public abstract Selector selector(); //返回与该键相关的Selector
public abstract void cancel(); //结束注册关系
public abstract boolean isValid(); //是否被取消了
public abstract int interestOps(); //返回注册时的interest集合
public abstract SelectionKey interestOps(int ops); //改变interest集合
public abstract int readyOps(); //获取相关通道的已经就绪的操作
public final boolean isReadable();
public final boolean isWritable();
public final boolean isConnectable();
public final boolean isAcceptable();
public final Object attach(Object ob); //连接一个Obj
public final Object attachment() //返回连接的Object
}
Selector中会维护的三种键集合
- 已注册的键集合:有些键可能已经取消了,keys()返回
- 已选择的键集合:已选择的键,已经准备好的通道。selectedKeys()返回
- 已取消的键集合:无法访问
简易NIO服务器
public class SelectorTest {
public static int PORT_NUMBER = 1234;
public void go(String...args) throws Exception {
int port = PORT_NUMBER;
System.out.println("Listening on port:"+port);
//打开ServerSocketChannel
ServerSocketChannel server = ServerSocketChannel.open();
//创建选择器
Selector selector = Selector.open();
//监听端口
server.bind(new InetSocketAddress(port));
server.configureBlocking(false);
//注册选择器
server.register(selector,SelectionKey.OP_ACCEPT);
while (true){
//会阻塞一段时间
int n = selector.select();
if (n == 0){
continue;
}
System.out.println("There are "+n+" ready Channels");
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey key = it.next();
if (key.isAcceptable()){
ServerSocketChannel ss = (ServerSocketChannel) key.channel();
SocketChannel channel = ss.accept();
registerChannel(selector,channel,SelectionKey.OP_READ);
sayHello(channel);
}
if (key.isReadable()){
readDataFromSocket(key);
}
it.remove();
}
}
}
protected void registerChannel(Selector selector, SelectableChannel channel
,int ops) throws Exception{
if (channel == null){
return;
}
channel.configureBlocking(false);
channel.register(selector,ops);
}
protected void readDataFromSocket(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel)key.channel();
int count;
buffer.clear();
while ((count = socketChannel.read(buffer)) > 0){
buffer.flip();
while (buffer.hasRemaining()){
socketChannel.write(buffer);
}
//System.out.println("Receiving data:"+new String(buffer.array())+"from ss");
buffer.clear();
}
if (count < 0){
socketChannel.close();
}
}
private void sayHello(SocketChannel channel) throws IOException{
buffer.clear();
buffer.put("Hi from Atalisas".getBytes());
buffer.flip();
channel.write(buffer);
}
private ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
public static void main(String...args) throws Exception {
new SelectorTest().go(args);
}
}
线程池的增强服务器
public class ThreadPoolSelectorServer extends SelectorTest {
private static final int MAX_THREADS = 15;
private ThreadPool pool = new ThreadPool(5);
public static void main(String...args) throws Exception {
new ThreadPoolSelectorServer().go(args);
}
@Override
protected void readDataFromSocket(SelectionKey key) throws IOException {
WorkerThread workerThread = pool.getWorker();
if (workerThread == null){
return;
}
workerThread.serviceChannel(key);
}
private class ThreadPool{
List<WorkerThread> idle = new LinkedList<>();
ThreadPool(int poolSize){
for (int i = 0;i < poolSize;i++){
WorkerThread thread = new WorkerThread(this);
thread.setName("Worker "+(i+1));
thread.start();
idle.add(thread);
}
}
WorkerThread getWorker(){
WorkerThread worker = null;
synchronized (idle){
if (idle.size() > 0){
worker = idle.remove(0);
}
}
return worker;
}
void returnWorker(WorkerThread thread){
synchronized (idle){
idle.add(thread);
}
}
}
private class WorkerThread extends Thread{
private ByteBuffer buffer = ByteBuffer.allocate(1024);
private ThreadPool pool;
private SelectionKey key;
WorkerThread(ThreadPool pool){
this.pool = pool;
}
public synchronized void run(){
System.out.println(this.getName()+" is ready");
while (true){
try {
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
this.interrupted();
}
if (key == null){
continue;
}
System.out.println(this.getName()+" has been awakened");
try{
drainChannel(key);
}catch (Exception e){
System.out.println("Caught '"+e+"' closing channel");
try {
key.channel().close();
}catch (IOException ex){
ex.printStackTrace();
}
key.selector().wakeup();
}
key = null;
this.pool.returnWorker(this);
}
}
synchronized void serviceChannel(SelectionKey key){
this.key = key;
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
this.notify();
}
void drainChannel(SelectionKey key) throws Exception{
SocketChannel channel = (SocketChannel)key.channel();
int count;
buffer.clear();
while ((count = channel.read(buffer)) > 0){
buffer.flip();
while (buffer.hasRemaining()){
channel.write(buffer);
}
buffer.clear();
}
if (count < 0){
channel.close();
return;
}
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
key.selector().wakeup();
}
}