hello小伙伴们大家好,我是新人吃饱了,今天带大家领略一下NIO的世界。
- 代码只是展示基本功能;
- 个人水平有限,如有疑问请及时指正;
1、NIO操作file
//标准IO下的文件读取与写入
public static void main3(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\121212\\111", "rw");
FileChannel fileChannel = file.getChannel();
ByteBuffer readBuffer = ByteBuffer.allocateDirect(5);
int read = fileChannel.read(readBuffer);
System.out.println("read:" + read);
System.out.println("readBuffer:" + readBuffer.toString());
//
ByteBuffer writeBuffer = ByteBuffer.allocateDirect(5);
writeBuffer.put(new byte[]{'!', '@', '#', '$', '%'});
writeBuffer.flip();
int write = fileChannel.write(writeBuffer, 0);
System.out.println("write:" + write);
System.out.println("success");
}
//内存映射,映射到的是堆外内存又叫做直接内存
public static void main2(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\121212\\111", "rw");
FileChannel fileChannel = file.getChannel();
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size());
//虽然映射磁盘文件减少了一次数据复制
System.out.println(buffer instanceof DirectBuffer); //true
ByteBuffer put = buffer.put(new byte[]{'a', 'b', 'c', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'});
buffer.get(new byte[12]);
System.out.println(buffer == put);//true
//buffer.force();
System.out.println("success");
//
}
//零拷贝
public static void main1(String[] args) throws Exception {
RandomAccessFile file1 = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\121212\\111", "rw");
FileChannel fileChannel1 = file1.getChannel();
//
RandomAccessFile file2 = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\121212\\1111", "rw");
FileChannel fileChannel2 = file2.getChannel();
//
long l = fileChannel1.transferTo(1, 10, fileChannel2);
System.out.println(l);
System.out.println("success");
//
}
2、NIO实现阻塞的客户端与服务端
//阻塞--客户端
public static void main4(String[] args) throws Exception {
SocketChannel socketChannel1 = SocketChannel.open();
socketChannel1.connect(new InetSocketAddress("127.0.0.1", 60001));
//
ByteBuffer writeBuffer = ByteBuffer.allocateDirect(5);
writeBuffer.put(new byte[]{'!', '@', '#', '$', '%'});
writeBuffer.flip();
int write = socketChannel1.write(writeBuffer);
System.out.println("write:" + write);
//
ByteBuffer readBuffer = ByteBuffer.allocate(5);
socketChannel1.read(readBuffer);
readBuffer.flip();
System.out.println("read:" + Arrays.toString(readBuffer.array()));
//
System.out.println("success");
}
//阻塞--服务端
public static void main5(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 60000));
SocketChannel channel;
while ((channel = serverSocketChannel.accept()) != null) {
System.out.println("channel:" + channel);
ByteBuffer readBuffer = ByteBuffer.allocate(5);
channel.read(readBuffer);
readBuffer.flip();
System.out.println("read:" + Arrays.toString(readBuffer.array()));
}
}
3、NIO实现Reactor模型(服务端)
应该注意对2方面的抽象:线程模型的抽象,实体的抽象;表格如下:
| 模型分类 | 优缺点 |
|---|---|
| 单reactor | 单个线程,单个selector,完成accept+read+write+业务逻辑处理,大量连接的情况下,CPU性能被浪费,容易成为系统瓶颈,redis使用的单reactor模型 |
| 单reactor+多线程 | 再上面的基础上,reactor所在线程负责accept+read+write,业务逻辑处理交由单独的线程池来完成,可以使用JDK的线程池 |
| 主从reactor | 分为2个线程池,都是Reactor线程,acceptor负责accept,reactor负责read+write+业务处理,netty一般使用这种模型,由于全部是NIO线程,所以要求不能阻塞 |
| 主从reactor+多线程 | 同第二种情况,一个acceptor,一个reactor线程池,再加上业务线程池,业务线程池可以阻塞,undertow使用的是该模式,一个默认的acceptor线程,ioThreads线程池负责read+write,workerThreads线程池负责阻塞的业务逻辑处理 |
3.1、单Reactor
package com.example.demo.test;
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.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
public class NioReactor2 {
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8989));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("ServerSocketChannel启动成功:" + ssc.hashCode());
while (true) {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isAcceptable()) {
ServerSocketChannel sc = (ServerSocketChannel) selectionKey.channel();
SocketChannel channel = sc.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
System.out.println("acceptable:" + channel.hashCode());
}
if (selectionKey.isReadable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
buffer.flip();
//需要使用attach方法传递数据
String message = new String(buffer.array());
String result = businessLogic(message);
System.out.println("read:" + message + ",经过处理后,需要返回:" + result);
channel.register(selector, SelectionKey.OP_WRITE, result);
}
if (selectionKey.isWritable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
Object result = selectionKey.attachment();
ByteBuffer buffer = ByteBuffer.allocate(20);
buffer.put(result.toString().getBytes(StandardCharsets.UTF_8));
buffer.flip();
channel.write(buffer);
System.out.println("write:" + Arrays.toString(buffer.array()));
channel.close();
}
}
selectionKeys.clear();
}
}
public static String businessLogic(String requestBody) {
return "QWER" + requestBody;
}
}
3.2、单Reactor+多线程
读取数据和写数据如何联动起来,返回读取的数据
package com.example.demo.test;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NioReactor2 {
//1
private static final ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8989));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("ServerSocketChannel启动成功:" + ssc.hashCode());
while (true) {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isAcceptable()) {
ServerSocketChannel sc = (ServerSocketChannel) selectionKey.channel();
SocketChannel channel = sc.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
System.out.println("acceptable:" + channel.hashCode());
}
if (selectionKey.isReadable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
buffer.flip();
String message = new String(buffer.array());
//2
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> asyncBusinessLogic(message));
completableFuture.thenAccept(result -> {
System.out.println("read:" + message + ",经过处理后,需要返回:" + result);
try {
channel.register(selector, SelectionKey.OP_WRITE, result);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
});
System.out.println("readable方法NIO线程执行完毕.....业务逻辑进入异步处理......");
}
if (selectionKey.isWritable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
Object result = selectionKey.attachment();
ByteBuffer buffer = ByteBuffer.allocate(20);
buffer.put(result.toString().getBytes(StandardCharsets.UTF_8));
buffer.flip();
channel.write(buffer);
System.out.println("write:" + Arrays.toString(buffer.array()));
channel.close();
}
}
selectionKeys.clear();
}
}
//3
public static String asyncBusinessLogic(String requestBody) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "QWER" + requestBody;
}
}
3.3、主从Reactor
package com.example.demo.test;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NioReactor1 {
static class Reactor {
private Selector selector;
private ExecutorService executorService;
public Reactor() {
try {
this.selector = Selector.open();
executorService = Executors.newSingleThreadExecutor();
} catch (Exception e) {
e.printStackTrace();
}
}
public void registerChannel(SocketChannel channel) {
try {
channel.register(this.selector, SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
public void run() {
executorService.execute(() -> {
try {
while (true) {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isReadable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
buffer.flip();
System.out.println("read:" + new String(buffer.array()));
channel.register(this.selector, SelectionKey.OP_WRITE);
}
if (selectionKey.isWritable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("abc".getBytes(StandardCharsets.UTF_8));
buffer.flip();
channel.write(buffer);
System.out.println("write:" + Arrays.toString(buffer.array()));
channel.close();
}
}
selectionKeys.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
static class Acceptor {
private Selector selector;
private ExecutorService executorService;
private Reactor reactor;
public Acceptor() {
try {
this.selector = Selector.open();
executorService = Executors.newSingleThreadExecutor();
} catch (Exception e) {
e.printStackTrace();
}
}
public void addSubReactor(Reactor reactor) {
this.reactor = reactor;
reactor.run();
}
public void run() {
if (this.reactor == null) {
System.err.println("未设置subReactor");
return;
}
executorService.execute(() -> {
try {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8989));
ssc.configureBlocking(false);
ssc.register(this.selector, SelectionKey.OP_ACCEPT);
System.out.println("ServerSocketChannel启动成功:" + ssc.hashCode());
while (true) {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel channel = serverSocketChannel.accept();
channel.configureBlocking(false);
reactor.registerChannel(channel);
System.out.println("acceptable:" + channel.hashCode());
}
}
selectionKeys.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
public static void main(String[] args) {
Reactor reactor = new Reactor();
Acceptor acceptor = new Acceptor();
acceptor.addSubReactor(reactor);
acceptor.run();
}
}
3.4、主从Reactor+多线程
参考2.2的实现方案
4、NIO实现客户端(阻塞&非阻塞)
当前线程以阻塞或者非阻塞的方式获取到结果,主要的方式:
- 当前线程发起一个连接请求,注册到Reactor线程池中,Reactor线程池负责connect,write,read,获取到数据后放入到公共的变量中
- 当前线程和Reactor线程通过公共变量ReadWriteContext进行交互,阻塞或非阻塞方式
- 关键难点在于线程的转换和交互,将请求加入到Reactor线程池后
reactor.register(socketChannel, readWriteContext);当前线程如何获取执行结果;
package com.flux.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ArrayList;
import java.util.List;
import java.util.Set;
public class Reactor3 {
private static final Logger logger = LoggerFactory.getLogger(Reactor3.class);
private static final List<Reactor> reactorList = new ArrayList<>();
static class ReadWriteContext {
private volatile byte[] writeData;
private volatile byte[] readData;
public ReadWriteContext(byte[] writeData) {
this.writeData = writeData;
}
public byte[] getWriteData() {
return writeData;
}
public void setWriteData(byte[] writeData) {
this.writeData = writeData;
}
public byte[] getReadData() {
return readData;
}
public void setReadData(byte[] readData) {
this.readData = readData;
}
}
static class HttpUtil {
public static String sendAndBlockingGet(int port, byte[] data) {
try {
//1、获取channel并将请求发送到Reactor线程池中,可以保存channel并从缓存中获取(连接池)
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(port));
ReadWriteContext readWriteContext = new ReadWriteContext(data);
//reactor负载均衡,这里简单化,只取第一个
reactorList.get(0).register(socketChannel, readWriteContext);
//2、线程交互(当前线程和reactor所在线程),判断是否有值,这里使用了循环阻塞的方式,浪费CPU资源
while (true) {
if (readWriteContext.getReadData() != null) {
return new String(readWriteContext.getReadData());
}
Thread.sleep(200);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
//将selector绑定到线程,并开始运行,并且提供对外的register接口
//selector+thread,整体看做一个reactor实例
static class Reactor {
private Selector selector;
public Reactor(int index) {
try {
this.selector = Selector.open();
Thread thread = new Thread(this::run);
thread.setName("Reactor-" + index);
thread.start();
} catch (Exception e) {
e.printStackTrace();
}
reactorList.add(this);
}
public void register(SocketChannel socketChannel, ReadWriteContext rwCtx) {
try {
socketChannel.register(this.selector, SelectionKey.OP_CONNECT, rwCtx);
} catch (Exception e) {
e.printStackTrace();
}
}
public void run() {
try {
while (true) {
selector.select(2000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
logger.info("selectionKeys-size:{}", selectionKeys.size());
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isConnectable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
channel.configureBlocking(false);
Selector selector = selectionKey.selector();
//3、判断连接是否完成
if (channel.finishConnect()) {
ReadWriteContext rwCtx = (ReadWriteContext) selectionKey.attachment();
logger.info("connect success:{}", channel.hashCode());
channel.register(selector, SelectionKey.OP_WRITE, rwCtx);
}
}
if (selectionKey.isWritable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
Selector selector = selectionKey.selector();
ReadWriteContext rwCtx = (ReadWriteContext) selectionKey.attachment();
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
byteBuffer.put(rwCtx.getWriteData());
byteBuffer.flip();
channel.write(byteBuffer);
logger.info("write success:{}", new String(rwCtx.getWriteData()));
channel.register(selector, SelectionKey.OP_READ, rwCtx);
}
if (selectionKey.isReadable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ReadWriteContext rwCtx = (ReadWriteContext) selectionKey.attachment();
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
channel.read(byteBuffer);
byteBuffer.flip();
rwCtx.setReadData(byteBuffer.array());
logger.info("read success:{}", new String(byteBuffer.array()));
channel.close();
}
}
selectionKeys.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//NIO-clint,阻塞和非阻塞的拿到数据
public static void main(String[] args) throws Exception {
//初始化多个reactor,这部分肯定是框架来处理的,休眠是为了通过日志判断reactor线程是否已经启动
new Reactor(0);
new Reactor(1);
Thread.sleep(10000);
//一般在业务场景中,我们通过如下方式来使用httpUtil发送请求并获取数据
String result = HttpUtil.sendAndBlockingGet(9898, "abc".getBytes());
System.out.println("result:" + result);
}
}
5、Netty实现SimpleHttpServer
- Acceptor接收到socket连接--->Reactor线程池read--->业务线程池---->Reactor线程池write
- 模拟undertow的线程模型
- 相比与NIO,增加了解码的配置,减少了线程模型的思考,体现了netty作为一个优秀的非阻塞IO框架的优势,极大简化了我们的开发
- 详细的netty示例可以查看官方文档:netty.io/,基于TCP的编解码器都有涉及;
- netty封装的NIO,本身是基于TCP&UDP的,http,ftp等均基于之上做的文本等协议,作为inboundHandler或者outboundHandler提供,我们只需要提供基础的businessHander即可;
package com.example.netty.demo.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class NettyServer {
static class Util {
public static void print(Object msg) {
System.out.println("[" + Thread.currentThread().getName() + "]:" + msg);
}
}
//自定义线程工厂,主要是为不同的线程修改名称
static class MyThreadFactory implements ThreadFactory {
AtomicInteger ai = new AtomicInteger(0);
String prefix = "";
public MyThreadFactory(String prefix) {
this.prefix = prefix;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName(prefix + ai.getAndIncrement());
return thread;
}
}
//Handles a server-side channel.
static class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
String uri = fullHttpRequest.uri();
ByteBuf content = fullHttpRequest.content();
Util.print("received,uri:" + uri + ";content:" + content.toString(CharsetUtil.UTF_8));
// 创建http响应
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.copiedBuffer("{\"result\":\"success\"}", CharsetUtil.UTF_8));
// 设置头信息,write到客户端
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
public static void main(String[] args) throws Exception {
//1、反应器线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(new MyThreadFactory("boss"));
EventLoopGroup workerGroup = new NioEventLoopGroup(new MyThreadFactory("worker"));
final EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(16, new MyThreadFactory("business"));
try {
//2、helper
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
//3、设置NIO的通道类型
.channel(NioServerSocketChannel.class)
.handler(new ChannelInitializer<ServerSocketChannel>() {
@Override
protected void initChannel(ServerSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Util.print("channelActive");
super.channelActive(ctx);
}
});
}
})
//4、装配子通道的Pipeline流水线,在父通道成功接收一个连接>创建一个子通道channel>调用ChannelInitializer实例的initChannel()
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
//解码器,或者使用HttpRequestDecoder & HttpResponseEncoder
ch.pipeline().addLast(new HttpServerCodec());
//对于post请求
ch.pipeline().addLast(new HttpObjectAggregator(65535));
//业务处理
ch.pipeline().addLast(businessGroup, new HttpRequestHandler());
}
})
//5、通道参数
.option(ChannelOption.SO_BACKLOG, 128)
//6、子通道参数
.childOption(ChannelOption.SO_KEEPALIVE, true);
//7、Bind and start to accept incoming connections.可以添加listener或者使用同步方式sync
ChannelFuture f = b.bind(8080).sync();
Util.print("EchoServer.runServer:端口绑定成功,服务端启动成功:8080");
//8、Wait until the server socket is closed.In this example, this does not happen, but you can do that to gracefully shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
最终的线程模型为:
C:\Users\qingwa>jstack 16324
2022-03-31 23:28:40
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):
"business0" #15 prio=5 os_prio=0 tid=0x000000001fb1e800 nid=0x3940 waiting on condition [0x000000002095e000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076d57f4b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at io.netty.util.concurrent.SingleThreadEventExecutor.takeTask(SingleThreadEventExecutor.java:243)
at io.netty.util.concurrent.DefaultEventExecutor.run(DefaultEventExecutor.java:64)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at java.lang.Thread.run(Thread.java:748)
"worker0" #14 prio=5 os_prio=0 tid=0x000000001f490800 nid=0x3a64 runnable [0x000000002085e000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x000000076d514338> (a io.netty.channel.nio.SelectedSelectionKeySet)
- locked <0x000000076d510898> (a java.util.Collections$UnmodifiableSet)
- locked <0x000000076d510748> (a sun.nio.ch.WindowsSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101)
at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:68)
at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:813)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:460)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at java.lang.Thread.run(Thread.java:748)
"boss0" #13 prio=5 os_prio=0 tid=0x000000001edca000 nid=0x2730 runnable [0x000000002075e000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x000000076d483a68> (a io.netty.channel.nio.SelectedSelectionKeySet)
- locked <0x000000076d040850> (a java.util.Collections$UnmodifiableSet)
- locked <0x000000076d03cb00> (a sun.nio.ch.WindowsSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101)
at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:68)
at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:813)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:460)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #12 daemon prio=9 os_prio=0 tid=0x000000001e7fc800 nid=0xd40 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
6、Netty实现SimpleHttpClient
参考官网例子即可