<>NIO基础
<>一、三大组件
<>1. Channel
读写数据的双向通道
常见Channel:
* FileChannel
* DatagramChannel:TCP UDP需要用到
* SocketChannel:客服端、服务端都可以用
* ServerSocketChannel:服务端可用
<>2. Buffer
内存缓存区,暂存数据,用来独写数据
常用Buffer:
* ByteBuffer
* MappedByteBuffer
* DirectByteBuffer
* HeapByteBuffer
* ShortBuffer
* IntBuffer
* 。。。。。。
<>2.1 ByteBuffer结构
重要属性:
* capacity:容量
* position:
* 写模式:代表写入数据的指针位置
* 读模式:将位置转换为读取位置
* limit:
* 写模式:写入限制
* 读模式:读取的限制
<>3. Selector
选择器
<>3.1 多线程版设计
<>3.2 缺点
* 内存占用高
* 线程上下文切换成本高
* 只适合连接少数的场景
<>3.3 线程池版本
<>3.4 缺点
* 阻塞模式下,线程仅能处理一个socket连接
* 仅适合短连接场景
<>3.5 Selector版本
配合一个线程来管理多个channel,获取这些channel上发生的事件,这些channel工作在非阻塞模式下。调用selector的select()
会阻塞知道channel发生了独写就绪事件,这些事件方法就会返回这些事件交给trhead来处理。适合流量比较低的场景
<>二、粘包\半包
网络上有多条数据发送给服务端,数据之间使用 \n 进行分割,但是由于某种原因这些数据在接收时被进行了重新组合。
public static void main(String[] args) throws IOException { //test1();
//writer(); ByteBuffer encode = StandardCharsets.UTF_8.encode("hello,world\ni
am zhangsan\nwe are family"); split(encode); encode = StandardCharsets.UTF_8.
encode(" together . \n 55555"); split(encode); encode = StandardCharsets.UTF_8.
encode(" nonononono . \n"); split(encode); encode = StandardCharsets.UTF_8.
encode(" 222222 . \n"); split(encode); encode = StandardCharsets.UTF_8.encode("
33333333333 . \n"); split(encode); encode = StandardCharsets.UTF_8.encode("
44444444444444 . \n123456\n"); split(encode); } /** * 粘包/半包,通过使用 /n分割
模拟网络发送的数据包 * 1、查询ThreadLocal中还有没有上一次没有读取完的数据 * 1.1 将上一次未获取完数据跟当前数据进行合并 * 1.2
开启读模式 * 1.3 验证 position的位置 跟limit的位置是否相同,如果不同证明 数据没有取完 * 2、读取数据 */ private
static void split(ByteBuffer byteBuffer){ //切换读模式 Data lastData =
dataThreadLocal.get(); if (Objects.nonNull(lastData)) { ByteBuffer
lastDataBuffer= lastData.getData(); ByteBuffer newByteBuffer = ByteBuffer.
allocate(byteBuffer.limit() + (lastDataBuffer.limit() - lastDataBuffer.position(
))); while (lastDataBuffer.hasRemaining()){ newByteBuffer.put(lastDataBuffer.get
()); } newByteBuffer.put(byteBuffer); newByteBuffer.flip(); //读取新传入的,将上一次的数据进行合并
for (int i = 0; i < newByteBuffer.limit(); i++) { byte b = newByteBuffer.get(i);
if ('\n' == b) { int length = i + 1 - newByteBuffer.position(); ByteBuffer
allocate= ByteBuffer.allocate(length); for (int i1 = 0; i1 < allocate.limit();
i1++) { allocate.put(newByteBuffer.get()); } allocate.flip(); System.out.println
("后续包数据:"+StandardCharsets.UTF_8.decode(allocate)); } } if (newByteBuffer.
position() != newByteBuffer.limit()) { //没有截取完的数据,将后面的数据放入 Data data = new Data(
Thread.currentThread().getName(), newByteBuffer); dataThreadLocal.set(data); } }
else { //将数据按照转义符进行分割 for (int i = 0; i < byteBuffer.limit() ; i++) { //截取数据
byte b = byteBuffer.get(i); if ('\n' == b) { int length = i + 1 - byteBuffer.
position(); ByteBuffer allocate = ByteBuffer.allocate(length); for (int i1 = 0;
i1< allocate.limit(); i1++) { allocate.put(byteBuffer.get()); } allocate.flip();
System.out.println("打印数据:"+StandardCharsets.UTF_8.decode(allocate)); } } if (
byteBuffer.position() != byteBuffer.limit()) { //没有截取完的数据,将后面的数据放入 Data data =
new Data(Thread.currentThread().getName(), byteBuffer); dataThreadLocal.set(data
); } } }
<>三、实战
<>时间服务器
public class TimeServer { public static void main(String[] args) throws
InterruptedException { int port = 8080; if (args != null && args.length > 0) {
try { port = Integer.parseInt(args[0]); } catch (NumberFormatException e) { // }
} new Thread(new MultiplexerTimeServer(port)).start(); Thread.currentThread().
join(); } public static class MultiplexerTimeServer implements Runnable {
//创建多路复用器 private Selector selector; //简历服务器管道 private ServerSocketChannel
serverSocketChannel; //用来控制服务器是否停止 private volatile boolean stop; public
MultiplexerTimeServer(int port) { try { selector = Selector.open();
serverSocketChannel= ServerSocketChannel.open(); //设置为非阻塞模式 serverSocketChannel.
configureBlocking(false); //绑定套接字 serverSocketChannel.socket().bind(new
InetSocketAddress(port), 1024); //注册一个接收的监听位事件 serverSocketChannel.register(
selector, SelectionKey.OP_ACCEPT); System.out.println("The time server is start
int port :" + port); } catch (IOException e) { System.exit(1); } } public void
stop() { this.stop = true; } @Override public void run() { try { while (!stop) {
//轮询注册在selector上面准备就绪的channel selector.select(1000); Set<SelectionKey>
selectionKeys= selector.selectedKeys(); Iterator<SelectionKey> keyIterator =
selectionKeys.iterator(); SelectionKey key = null; while (keyIterator.hasNext())
{ key = keyIterator.next(); keyIterator.remove(); this.handleInput(key); } } }
catch (IOException e) { e.printStackTrace(); } } /** * 处理事件 * @param
selectionKey * @throws IOException */ private void handleInput(SelectionKey
selectionKey) throws IOException { if (selectionKey.isValid()) {
//如果是接收事件先处理接收的事件,然后再在管道上面注册一个读的操作位 if (selectionKey.isAcceptable()) { System.
out.println("接收到连接事件。。。。"); ServerSocketChannel channel = (ServerSocketChannel)
selectionKey.channel(); SocketChannel sc = channel.accept(); sc.
configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); }
//如果是读事件 if (selectionKey.isReadable()) { System.out.println("接收到读事件。。。。");
SocketChannel channel = (SocketChannel) selectionKey.channel(); ByteBuffer
byteBuffer= ByteBuffer.allocate(1024); int read = channel.read(byteBuffer);
byteBuffer.flip(); if (read > 0) { byte[] bytes = new byte[byteBuffer.remaining(
)]; byteBuffer.get(bytes); String body = new String(bytes, StandardCharsets.
UTF_8); System.out.println("The time server receive order :" + body); //返回客户端时间
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System
.currentTimeMillis()).toString() : "BAD ORDER"; doWrite(channel, currentTime); }
else if (read < 0) { selectionKey.cancel(); channel.close(); } else { ; //读取 0
字节 忽略 } } } } private void doWrite(SocketChannel socketChannel, String response)
throws IOException { if (StringUtils.hasText(response)) { byte[] bytes =
response.getBytes(StandardCharsets.UTF_8); ByteBuffer wrap = ByteBuffer.allocate
(bytes.length); wrap.put(bytes); wrap.flip(); socketChannel.write(wrap); } } } }
<>时间客户端
public class TimeClient { public static void main(String[] args) throws
InterruptedException { int port = 8080; if (args != null && args.length > 0) {
port= Integer.parseInt(args[0]); } new Thread(new TimeClientHandle("127.0.0.1",
port)).start(); Thread.currentThread().join(); } public static class
TimeClientHandle implements Runnable { private String host; private int port;
private Selector selector; private SocketChannel socketChannel; private volatile
boolean stop; public TimeClientHandle(String host, int port) { this.host = host
== null ? "127.0.0.1" : host; this.port = port; try { selector = Selector.open()
; socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false);
} catch (IOException e) { e.printStackTrace(); System.exit(1); } } @Override
public void run() { try { doConnect(); } catch (IOException e) { e.
printStackTrace(); System.exit(1); } while (!stop) { try { selector.select(1000)
; Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<
SelectionKey> selectionKeyIterator = selectionKeys.iterator(); SelectionKey key
= null; while (selectionKeyIterator.hasNext()) { key = selectionKeyIterator.next
(); selectionKeyIterator.remove(); try { handleInput(key); } catch (IOException
e) { e.printStackTrace(); key.cancel(); if (key.channel() != null) { key.channel
().close(); } } } } catch (Exception e) { e.printStackTrace(); System.exit(1); }
} } /** * 创建连接 * 1.如果直接连接成功,直接注册读事件到多路复用器上 * 2.没有连接成功的话,注册连接事件到多路复用器上 * @throws
IOException */ private void doConnect() throws IOException { if (socketChannel.
connect(new InetSocketAddress(host, port))) { socketChannel.register(selector,
SelectionKey.OP_READ); doWrite(socketChannel); } else { socketChannel.register(
selector, SelectionKey.OP_CONNECT); } } /** * 发送请求数据到管道中 * * * @param
socketChannel * @throws IOException */ private void doWrite(SocketChannel
socketChannel) throws IOException { byte[] bytes = "QUERY TIME ORDER".getBytes(
StandardCharsets.UTF_8); ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.
length); byteBuffer.put(bytes); byteBuffer.flip(); socketChannel.write(
byteBuffer); if (!byteBuffer.hasRemaining()) { System.out.println("Send order 2
server succeed ."); } } /** * 先判断key 是否是连接事件,如果连接成功,注册一个读时间发送查询请求 * * @param
selectionKey * @throws IOException */ private void handleInput(SelectionKey
selectionKey) throws IOException { if (selectionKey.isValid()) { SocketChannel
socketChannel= (SocketChannel) selectionKey.channel(); //判断是否连接成功 if (
selectionKey.isConnectable()) { System.out.println("接收到服务器连接事件。。。。");
//完成连接后,注册一个读事件 if (socketChannel.finishConnect()) { socketChannel.register(
selector, SelectionKey.OP_READ); doWrite(socketChannel); } } //读事件准备就绪 if (
selectionKey.isReadable()) { System.out.println("接收到服务器读事件。。。。"); ByteBuffer
byteBuffer= ByteBuffer.allocate(1024); int read = socketChannel.read(byteBuffer)
; if (read > 0) { byteBuffer.flip(); byte[] bytes = new byte[read]; byteBuffer.
get(bytes); String body = new String(bytes, StandardCharsets.UTF_8); System.out.
println("Now is :" + body); this.stop = true; } else if (read < 0) {
selectionKey.cancel(); socketChannel.close(); } else { ; // 读取 0 字节 } } } } } }