目录
IO与NIO的区别:
NIO的三大核心:
- Buffer
- Channel
- Selector
1. Buffer
1.1 定义
一个 Buffer 本质上是内存中的一块, 可以将数据写入这块内存, 从这块内存获取数据
java.nio 定义了以下几个 Buffer 的实现:
Java NIO Buffer三大核心属性:position、limit、capacity
capacity: 作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
position: 当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity.
当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
limit: 在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
(1) capacity代表这个缓冲区的容量,一旦设定就不可以更改。比如 capacity 为 1024 的 IntBuffer,代表其一次可以存放 1024 个 int 类型的值。一旦 Buffer 的容量达到 capacity,需要清空 Buffer,才能重新写入值
(2) 从写操作模式到读操作模式切换的时候(flip),position 都会归零,这样就可以从头开始读写了; 写操作模式下,limit 代表的是最大能写入的数据,这个时候 limit 等于 capacity; 写结束后,切换到读模式,此时的 limit 等于 Buffer 中实际的数据大小,因为 Buffer 不一定被写满了
(3) buffer从分配内存上分为DirectByteBuffer和HeapByteBuffer
HeapByteBuffer
- HeapByteBuffer, 标准的java类
- 维护一份byte[]在JVM堆上
- 创建开销小
HeapByteBuffer优点:由于内容维护在jvm里,所以把内容写进buffer里速度会快些;并且,可以更容易回收;
为什么在使用HeapByteBuffer时读取数据时需要把数据先从堆内存中拷贝到堆外内存,再进行操作?
因为绝大多数的gc算法(CMS除外)在垃圾回收的时候都会移动内存中的数据,导致发生gc之后内存地址也会发生变化,内核空间在读取数据的时候容易出现问题,所以会先把heap中的内存拷贝到堆外。所以希望优化IO性能的话,数据使用堆外内存可以减少一次内存的拷贝消耗。
DirectByteBuffer
- 底层存储在非JVM堆上,通过native代码操作
- -XX:MaxDirectMemorySize=<size>
- 创建开销大
DirectByteBuffer优点:跟外部设备(IO设备)打交道时会快很多,因为外部设备读取jvm堆里的数据时,不是直接读取的,而是把jvm里的数据读到一个内存块里,再在这个块里读取的,如果使用DirectByteBuffer,则可以省去这一步,实现zero copy(零拷贝);
DirectByteBuffer与HeapByteBuffer的比较:
|
DirectByteBuffer |
HeapByteBuffer |
创建开销 |
大 |
小
|
存储位置 |
Native heap |
Java heap |
数据拷贝 |
无需临时缓冲区做拷贝 |
拷贝到临时DirectByteBuffer,但临时缓冲区使用缓存。 聚集写/发散读时没有缓存临时缓冲区。 |
GC影响 |
每次创建或者释放的时候 都调用一次System.gc() |
|
1.2 Buffer的创建
(1) allocate/allocateDirect 方法
使用示例:
public class BufferCreate {
public static void main(String[] args) {
/**
* 堆内存
*/
System.out.println("======allocate======");
ByteBuffer buffer0 = ByteBuffer.allocate(10);
if (buffer0.hasArray()) {
System.out.println("buffer0 array: " + buffer0.array());
System.out.println("Buffer0 array offset: " + buffer0.arrayOffset());
}
System.out.println("Capacity: " + buffer0.capacity());
System.out.println("Limit: " + buffer0.limit());
System.out.println("Position: " + buffer0.position());
System.out.println("Remaining: " + buffer0.remaining());
System.out.println();
/**
* 直接内存
*/
System.out.println("======allocateDirect======");
ByteBuffer buffer1 = ByteBuffer.allocateDirect(10);
if (buffer1.hasArray()) { //非数组
System.out.println("buffer1 array: " + buffer1.array());
System.out.println("Buffer1 array offset: " + buffer1.arrayOffset());
}
System.out.println("Capacity: " + buffer1.capacity());
System.out.println("Limit: " + buffer1.limit());
System.out.println("Position: " + buffer1.position());
System.out.println("Remaining: " + buffer1.remaining());
System.out.println();
}
}
控制台输出:
======allocate======
buffer0 array: [B@7852e922
Buffer0 array offset: 0
Capacity: 10
Limit: 10
Position: 0
Remaining: 10======allocateDirect======
Capacity: 10
Limit: 10
Position: 0
Remaining: 10
(2) wrap方法
使用示例:
public class BufferCreate {
public static void main(String[] args) {
byte[] bytes = new byte[10];
System.out.println("======wrap======");
ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
if (buffer2.hasArray()) {
System.out.println("buffer2 array: " + buffer2.array());
System.out.println("Buffer2 array offset: " + buffer2.arrayOffset());
}
System.out.println("Capacity: " + buffer2.capacity());
System.out.println("Limit: " + buffer2.limit());
System.out.println("Position: " + buffer2.position());
System.out.println("Remaining: " + buffer2.remaining());
System.out.println();
byte[] bytes2 = new byte[10];
ByteBuffer buffer3 = ByteBuffer.wrap(bytes2, 2, 3);
if (buffer3.hasArray()) {
System.out.println("buffer3 array: " + buffer3.array());
System.out.println("Buffer3 array offset: " + buffer3.arrayOffset());
}
System.out.println("Capacity: " + buffer3.capacity());
System.out.println("Limit: " + buffer3.limit());
System.out.println("Position: " + buffer3.position());
System.out.println("Remaining: " + buffer3.remaining());
}
}
控制台输出:
======wrap======
buffer2 array: [B@4e25154f
Buffer2 array offset: 0
Capacity: 10
Limit: 10
Position: 0
Remaining: 10buffer3 array: [B@70dea4e
Buffer3 array offset: 0
Capacity: 10
Limit: 5
Position: 2
Remaining: 3
1.3 Buffer的读取
- put/get()方法: 向buffer中写或者读数据
- flip()方法: 将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。
换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等 - mark()/reset()方法: 通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:buffer.mark(); //添加标记 buffer.reset();//恢复到标记位置
- compact()/clear()方法: 一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。
如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么使用compact()方法。compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
- rewind()方法: 将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
使用示例:
public class BufferAccess {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10); //创建buffer, 默认为写模式
printBuffer(buffer);
buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
printBuffer(buffer);
System.out.println("========切换到读取模式=======");
buffer.flip(); //切换到读取模式
printBuffer(buffer);
//读取buffer
System.out.println("" + (char) buffer.get() + (char) buffer.get());
printBuffer(buffer);
buffer.mark();
printBuffer(buffer);
//读取两个元素后,恢复到之前mark的位置处
System.out.println("" + (char) buffer.get() + (char) buffer.get());
printBuffer(buffer);
buffer.reset(); // 使用mark()方法标记后, 可以使用reset()方法, 将指针回到mark标记的位置
//buffer.rewind();
printBuffer(buffer);
buffer.compact();
printBuffer(buffer);
buffer.clear();
printBuffer(buffer);
}
private static void printBuffer(Buffer buffer) {
System.out.println("[limit=" + buffer.limit()
+", position = " + buffer.position()
+", capacity = " + buffer.capacity()
+", array = " + buffer.toString()+"]");
}
}
控制台输出:
[limit=10, position = 0, capacity = 10, array = java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]]
[limit=10, position = 5, capacity = 10, array = java.nio.HeapByteBuffer[pos=5 lim=10 cap=10]]
========切换到读取模式=======
[limit=5, position = 0, capacity = 10, array = java.nio.HeapByteBuffer[pos=0 lim=5 cap=10]]
He
[limit=5, position = 2, capacity = 10, array = java.nio.HeapByteBuffer[pos=2 lim=5 cap=10]]
[limit=5, position = 2, capacity = 10, array = java.nio.HeapByteBuffer[pos=2 lim=5 cap=10]]
ll
[limit=5, position = 4, capacity = 10, array = java.nio.HeapByteBuffer[pos=4 lim=5 cap=10]]
[limit=5, position = 2, capacity = 10, array = java.nio.HeapByteBuffer[pos=2 lim=5 cap=10]]
[limit=10, position = 3, capacity = 10, array = java.nio.HeapByteBuffer[pos=3 lim=10 cap=10]]
[limit=10, position = 0, capacity = 10, array = java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]]
1.4 Buffer复制 – 浅复制
- duplicate方法: 调用duplicate方法返回的Buffer对象就是复制了一份原始缓冲区,复制了position、limit、capacity这些属性(拥有独立的posittion,limit,capacity属性),但是复制后的缓冲区get和put所操作的数组还是与原始缓冲区一样的,所以对复制后的缓冲区进行修改也会修改原始的缓冲区
- asReadOnlyBuffer方法: 调用asReadOnlyBuffer方法返回的Buffer对象就是复制了一份原始缓冲区,复制了position、limit、capacity这些属性, 但是复制后的buffer对象只可读, 不能对底层数据进行修改
- slice方法: 返回一个新的buffer对象,这个新buffer和老buffer公用一个内存。但是被start和end索引偏移缩减了; (比如,一个buffer里有1到10个字节,我们只想要4-8个字节,就可以用这个函数buf.slice(4,8),因为他们共用一个内存,所以不会消耗内存) 因为共用内存,所以修改新的buffer后,老buffer的内容同样也会被修改; buffer.slice([start], [end])
slice()方法使用示例:
public class SliceBuffer{
static public void main( String args[] ) throws Exception {
ByteBuffer buffer = ByteBuffer.allocate( 10 ); //创建一个buffer
for (int i=0; i<buffer.capacity(); ++i) {
buffer.put( (byte)i ); //向buffer中写入值
}
buffer.position(3); //将buffer的position属性设为3,limit属性设为7, 在后面循环中就只能操作[3,7)之间的内容
buffer.limit(7);
ByteBuffer slice = buffer.slice();//使用slice()进行复制
for (int i=0; i<slice.capacity(); ++i) {
byte b = slice.get( i );
b *= 11;
slice.put( i, b ); //修改数据
}
buffer.position( 0 ); //将buffer的position属性设为0,limit属性设为10,以便输出所有内容
buffer.limit( buffer.capacity() );
while (buffer.remaining()>0) {
System.out.println( buffer.get() );
}
}
}
控制台输出:
0
1
2
33
44
55
66
7
8
9
public class DuplicateBuffer {
public static void main(String[] args) {
CharBuffer buffer = CharBuffer.allocate(8); //创建buffer对象
for(int i= 0 ; i < buffer.capacity() ; i++) {
buffer.put(String.valueOf(i).charAt(0));
}
printBuffer(buffer);
buffer.flip(); //切换到读模式
printBuffer(buffer);
buffer.position(3).limit(6).mark().position(5);
printBuffer(buffer);
CharBuffer dupeBuffer = buffer.duplicate();
buffer.clear();
printBuffer(buffer);
printBuffer(dupeBuffer);
dupeBuffer.clear();
printBuffer(dupeBuffer);
}
private static void printBuffer(Buffer buffer) {
System.out.println("[limit=" + buffer.limit()
+", position = " + buffer.position()
+", capacity = " + buffer.capacity()
+", array = " + buffer.toString()+"]");
}
}
使用示例:
[limit=8, position = 8, capacity = 8, array = ]
[limit=8, position = 0, capacity = 8, array = 01234567]
[limit=6, position = 5, capacity = 8, array = 5]
[limit=8, position = 0, capacity = 8, array = 01234567]
[limit=6, position = 5, capacity = 8, array = 5]
[limit=8, position = 0, capacity = 8, array = 01234567]
2. Channel
所有的 NIO 操作始于通道,通道是数据来源或数据写入的目的地,主要地(Channel可以比作水管, buffer可以比作水池, 读写操作可以看做是进水放水操作); java.nio 包中主要实现的以下几个 Channel:
- FileChannel:文件通道,用于文件的读和写
- DatagramChannel:用于 UDP 连接的接收和发送
- SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端
- ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求
使用示例:
public class CopyFile {
static public void main(String args[]) throws Exception {
String infile = "src/main/resources/CopyFile.java";
String outfile = "src/main/resources/CopyFile.java.copy";
// 从流中获取通道
FileInputStream fin = new FileInputStream(infile);
FileOutputStream fout = new FileOutputStream(outfile);
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
// 读入之前要清空
buffer.clear();
// position自动前进
int r = fcin.read(buffer);
if (r == -1) {
break;
}
// position = 0; limit=读到的字节数
buffer.flip();
// 从 buffer 中读
fcout.write(buffer);
}
}
}
3.Selector
Selector是Java NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读、可写; 可以实现通过单线程管理多个channel,也就是可以管理多个网络链接
1. 创建Selector
Selector selector = Selector.open();
2. 注册Channel到Selector上
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
register的第二个参数,这个参数是一个“关注集合”,代表关注的channel状态,
有四种基础类型可供监听, 用SelectionKey中的常量表示如下(SelectionKey表示Selector和被注册的channel之间关系,保存channel感兴趣的事件)
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
3. 从Selector中选择channel; 一旦向Selector注册了一个或多个channel后,就可以调用select来获取channel; select方法会返回所有处于就绪状态的channel
select方法具体如下:
int select()
int select(long timeout)
int selectNow()
select()方法的返回值是一个int,代表有多少channel处于就绪了。也就是自上一次select后有多少channel进入就绪
selectedKeys(): 在调用select并返回了有channel就绪之后,可以通过选中的key集合来获取channel,这个操作通过调用selectedKeys()方法: Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
NIO的优点:
1. 事件驱动模型
- 避免多线程
- 单线程处理多任务
2. 非阻塞IO,IO读写不再阻塞,而是返回0
3. 基于block的传输,通常比基于流的传输更高效
4. 更高级的IO函数,zero-copy
5. IO多路复用大大提高了java网络应用的可伸缩性和实用性
测试示例(内容较长, 可以跳过):
服务器类:
package com.lic.demo.nio.demo;
import java.io.IOException;
public class NIOEchoServer {
public static void main(String[] args) throws IOException {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
EchoHandler timeServer = new EchoHandler(port);
new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
}
}
服务端处理器类:
package com.lic.demo.nio.demo;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class EchoHandler implements Runnable {
private Selector selector;
private ServerSocketChannel servChannel;
private volatile boolean stop;
private int num = 0;
public EchoHandler(int port) {
try {
selector = Selector.open();
servChannel = ServerSocketChannel.open();//开启一个服务端通道
servChannel.configureBlocking(false);
servChannel.socket().bind(new InetSocketAddress(port), 1024);
servChannel.register(selector, SelectionKey.OP_ACCEPT); //注册通道
System.out.println("服务器在端口[" + port + "]等待客户请求......");
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
this.stop = true;
}
@Override
public void run() {
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
// 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
if (selector != null)
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
// 处理新接入的请求消息
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = ssc.accept(); // Non blocking, never null
socketChannel.configureBlocking(false);
SelectionKey sk = socketChannel.register(selector, SelectionKey.OP_READ);
sk.attach(num++);
}
if (key.isReadable()) {
// 读取数据
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("来自客户端[" + key.attachment() + "]的输入: [" + body.trim() + "]!");
if (body.trim().equals("Quit")) {
System.out.println("关闭与客户端[" + key.attachment() + "]......");
key.cancel();
sc.close();
} else {
String response = "来自服务器端的响应:" + body;
doWrite(sc, response);
}
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else {
}
}
}
}
private void doWrite(SocketChannel channel, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
客户端类:
package com.lic.demo.nio.demo;
public class NIOEchoClient {
public static void main(String[] args) {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
new Thread(new NIOEchoClientHandler("127.0.0.1", port), "NIOEchoClient-001").start();
}
}
客户端处理器类:
package com.lic.demo.nio.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NIOEchoClientHandler implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private ExecutorService executorService;
private volatile boolean stop;
public NIOEchoClientHandler(String host, int port) {
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
this.executorService= Executors.newSingleThreadExecutor();
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
try {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress(host, port));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
// 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(executorService != null) {
executorService.shutdown();
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
// 判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
System.out.println("连接到服务器......");
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("请输入消息[输入\"Quit\"]退出:");
executorService.submit(() -> {
while(true) {
try {
buffer.clear();
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(input);
String msg = br.readLine();
if (msg.equals("Quit")) {
System.out.println("关闭客户端......");
key.cancel();
sc.close();
this.stop = true;
break;
}
buffer.put(msg.getBytes());
buffer.flip();
sc.write(buffer);
System.out.println("请输入消息[输入\"Quit\"]退出:");
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
sc.register(selector, SelectionKey.OP_READ);
} else {
System.exit(1); // 连接失败,进程退出
}
}
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println(body);
if(body.equals("Quit"))
{
this.stop = true;
}
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
if(key.isWritable()){
System.out.println("The key is writable");
}
}
}
private void doWrite(SocketChannel sc) throws IOException {
/* System.out.println("请输入消息[输入\"Quit\"]退出:");
BufferedReader stdIn = new BufferedReader(new InputStreamReader(
System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println(in.readLine());*/
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if (!writeBuffer.hasRemaining())
System.out.println("Send order 2 server succeed.");
/*
if (userInput.equals("Quit")) {
System.out.println("Closing client");
out.close();
in.close();
stdIn.close();
echoSocket.close();
System.exit(1);
}
System.out.println("请输入消息[输入\"Quit\"]退出:");
}*/
}
}
来源:CSDN
作者:AnEra
链接:https://blog.csdn.net/qq_38975553/article/details/104154730