HelloWorld
public class HelloServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());// 解码
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { // 自定义事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 处理读事件
System.out.println(msg.toString());
}
});
}
}).bind("localhost", 8080);
}
}
public class HellClient {
public static void main(String[] args) throws InterruptedException {
new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
}).connect(new InetSocketAddress("localhost", 8080))
.sync()
.channel()
.writeAndFlush("hello world")
;
}
}
概念
- 把channel立即为数据的通道
- 把msg理解为流动的数据,最开始输入是ByteBuf,但经过pipeline的加工,会变成其他类型对象,最后输出又变成ByteBuf
- 把handler理解为数据处理的工序
- 工序有多道,合在一起就是pipeline,pipeline负责发布事件(读、读取完成)传播给每个handler,handler对自己感兴趣的事件进行处理(重写了相应事件处理方法)
- handler分为Inbound(入站,写入数据)和Outbound(出战,写出数据)两类
- 把eventloog理解为处理数据的工人
- 工人可以管理多个channel的io操作,并且一旦工人负责了某个channel,就要负责到底(绑定)
- 工人既可以执行io操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个channel的待处理任务,任务分为普通任务,定时任务
- 工人按照pipeline的顺序,依次按照handler的规划处理数据,可以为每道工序指定不同的工人
EventLoop
EventLoop本质是一个单线程执行器(同时维护了一个Selector),里面有run方法处理channel上源源不断的的io事件
他的继承关系比较复杂
- 一条线是继承自juc.ScheduleExecutorService因此包含了线程池中的所有方法
- 另一条线是继承自netty自己的OrderedEventExecutor
- 提供了boolean inEventLoop(Thread thread)方法判断一个线程是否属于此EventLoop
- 提供了parent方法来看看自己属于哪个EventLoopGroup
EventLoopGroup是一组EventLoop,Channel一般会调用EventLoopGroup的register方法绑定其中一个EventLoop,后续这个Channel上的io事件都由此EventLoop来处理(保证了io事件处理时的线程安全
)
- 继承自netty自己的EventExecutorGroup
- 实现了Iterable接口提供遍历EventLoop的能力
- 另有next方法获取集合的下一个EventLoop
@Slf4j
public class TestEventLoop {
public static void main(String[] args) {
// 1 创建事件循环组
NioEventLoopGroup group = new NioEventLoopGroup(2);// io事件,普通任务,定时任务
DefaultEventLoopGroup defaultGroup = new DefaultEventLoopGroup();// 普通任务,定时任务
// 如果没有指定线程数 使用 系统核心数*2
// System.out.println(NettyRuntime.availableProcessors());
// System.out.println(Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)));
// 2 获取下一个时间循环对象
System.out.println(group.next());
System.out.println(group.next());
System.out.println(group.next());
System.out.println(group.next());
System.out.println(group.next());
// 3 执行普通任务, 意义: 异步处理
/*group.next().submit(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
log.debug("ok");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
log.info("main ---- ");*/
// 4 定时任务
group.next().scheduleAtFixedRate(new Runnable() {
public void run() {
log.info("每隔1s ok");
}
},3,1, TimeUnit.SECONDS);
}
}
boss 和 worker
public class HelloServer {
public static void main(String[] args) {
new ServerBootstrap()
// boss 处理 连接 worker 处理读写
.group(new NioEventLoopGroup(),new NioEventLoopGroup(2))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(ctx.channel().eventLoop());
ByteBuf buf = (ByteBuf) msg;
System.out.println(buf.toString(Charset.defaultCharset()));
}
});
}
}).bind(9999);
}
}
耗时操作再次分工
public class HelloServer {
public static void main(String[] args) {
final DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();
new ServerBootstrap()
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(
defaultEventLoopGroup, "default", new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println(buf);
System.out.println(ctx.channel().eventLoop());
ctx.fireChannelRead(msg);
}
}
).addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println(ctx.channel().eventLoop());
System.out.println(buf);
}
});
}
}).bind(9999);
}
}
Channel
Channel 的主要作用
- close()可以用来关闭channel
- closeFuture()用来处理channel的关闭
- sync方法作用是同步等待channel关闭
- 而addListener方法是异步等待channel关闭
- pipeline()方法添加处理器
- write()方法将数据写入
- writeAndFlush()方法将数据写入并刷出
@Slf4j
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
// 1 连接到服务器
// 异步非阻塞,main 发起了调用,真正执行connect 是 nio线程
.connect(new InetSocketAddress("127.0.0.1", 9999));
//channelFuture.sync();
// 无阻塞向下执行获取channel
Channel channel = channelFuture.channel();
log.info("{} ----------",channel);
// 向服务端发送数据
channel.writeAndFlush("hello world");
}
}
// 此时是拿不到channel连接对象的
两种解决方式
- sync方法作用是同步等待channel关闭
- addListener方法是异步等待channel关闭
@Slf4j
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
// 1 连接到服务器
// 异步非阻塞,main 发起了调用,真正执行connect 是 nio线程
.connect(new InetSocketAddress("127.0.0.1", 9999));
///1. 使用sync方法同步处理结果
// channelFuture.sync();
// Channel channel = channelFuture.channel();
// log.info("{} ----------",channel);
// channel.writeAndFlush("hello world");
///2. 使用addListener(回掉对象)方法异步处理结果
channelFuture.addListener(new ChannelFutureListener() {
// 在nio 线程连接建立好之后,会调用operationComplete
public void operationComplete(ChannelFuture channelFuture) throws Exception {
Channel channel = channelFuture.channel();
log.info("{} ----------", channel);
channel.writeAndFlush("hello world");
}
});
}
}
channel关闭之后优雅的做一些善后操作
@Slf4j
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
// 添加hanndler日志
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 9999));
Channel channel = channelFuture.sync().channel();
new Thread( ()->{
Scanner scanner = new Scanner(System.in);
while (true) {
String s = scanner.nextLine();
if ("q".equals(s)) {
channel.close();
break;
}
channel.writeAndFlush(s);
}
},"input").start();
ChannelFuture closedFuture = channel.closeFuture();
/// 关闭之后要做的事,善后操作
/* closedFuture.sync();
log.info("处理关闭之后的事");*/
closedFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
log.info("处理关闭之后的事");
group.shutdownGracefully();// 不是立刻关闭, 优雅的关闭,哈哈哈
}
});
}
}
netty 异步理解
netty 采用以下划分
要点:
- 单线程没法异步提高效率,必须配合多线程、多核cpu才能发挥异步的优势
- 吞吐量提升(单位时间内处理请求的数量)
- 合理进行任务拆分,也是利用异步的关键
Future & Promise
Jdk future < netty future < promise
在异步处理事,经常用到这两个接口
首先要说明netty中的Future于jdk中国呢的Future同名,但是是两个接口,netty的Future继承自jdk的Future,而Promise又对netty Future进行了扩展
- jdk Future只能同步等待任务结束(成功或失败)才能得到结果
- netty Future可以同步等待,也可以异步得到结果,但是都要等待任务结束
- netty Promise不仅有netty Future的功能,而且脱离了任务独立存在,只做为两个线程间传递结果的容器
名称 | jdk | netty | promise |
---|---|---|---|
cancel | 取消任务 | ||
isCanceled | 任务是否取消 | ||
isDone | 任务是否完成,不能区分成功失败 | ||
get | 获取任务结果,阻塞等 | ||
getNow | 获取任务结果,非阻塞,还未产生结果返回null | ||
await | 等待,如果任务失败,不会抛异常,而是通过isSuccess判断 | ||
sync | 等待任务结束,如果失败,抛异常 | ||
isSuccess | 是否成功 | ||
cause | 获取失败信息,非阻塞,如果没有失败,返回null | ||
addListener | 添加回掉,异步接受结果 | ||
setSuccess | 设置成功 | ||
setFailure | 设置失败 |
public class TestFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*ExecutorService service = Executors.newCachedThreadPool();
Future<String> future = service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("执行结算");
Thread.sleep(3000);
return "hello";
}
});
/// 同步等待结果
String s = future.get();
System.out.println(s);*/
/* NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
EventLoop eventLoop = eventExecutors.next();
Future<String> future = eventLoop.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("执行结算");
Thread.sleep(3000);
return "hello";
}
});
// 阻塞
// String now = future.get();
// 阻塞
// future.await();
// String now = future.getNow();
// 异步非阻塞
future.addListener(new GenericFutureListener<Future<? super String>>() {
@Override
public void operationComplete(Future<? super String> future) throws Exception {
System.out.println(future.get());
}
});*/
// promise
DefaultEventLoopGroup group = new DefaultEventLoopGroup();
EventLoop eventLoop = group.next();
DefaultPromise<String> defaultPromise = new DefaultPromise<>(eventLoop);
new Thread(() -> {
System.out.println("计算");
try {
Thread.sleep(5000);
System.out.println(1 / 0);
} catch (InterruptedException e) {
defaultPromise.setFailure(new Exception());
throw new RuntimeException(e);
}
defaultPromise.setSuccess("hello");
}).start();
defaultPromise.addListener(new GenericFutureListener<Future<? super String>>() {
@Override
public void operationComplete(Future<? super String> future) throws Exception {
System.out.println(future.get());
}
});
}
}
Handler & Pipeline
ChannelHandler用来处理Channel上的各种事件,分为入站、出战两种。所有ChannelHandler被连成一串
执行顺序演示
@Slf4j
public class HelloServer {
public static void main(String[] args) {
final DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();
new ServerBootstrap()
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
// 总是 head - h1 - h2 - h3 - h4 - h5 - h6- tail
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("h1",new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("------1-----------");
ByteBuf buf = (ByteBuf) msg;
System.out.println(ctx.channel().eventLoop());
System.out.println(buf.toString());
super.channelRead(ctx,msg);// 数据读传递 两者二选一 ctx.fireChannelRead(msg);
}
});
pipeline.addLast("h2",new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("------2-----------");
ByteBuf buf = (ByteBuf) msg;
System.out.println(ctx.channel().eventLoop());
System.out.println(buf.toString());
super.channelRead(ctx,msg);
}
});
pipeline.addLast("h3",new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("------3-----------");
ByteBuf buf = (ByteBuf) msg;
System.out.println(ctx.channel().eventLoop());
System.out.println(buf.toString());
ctx.channel().writeAndFlush("hello wlfds");
super.channelRead(ctx,msg);
}
});
pipeline.addLast("h4",new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.info("------4-----------");
super.write(ctx, msg, promise);
}
});
pipeline.addLast("h5",new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.info("------5-----------");
super.write(ctx, msg, promise);
}
});
pipeline.addLast("h6",new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.info("------6-----------");
super.write(ctx, msg, promise);
}
});
}
}).bind(9999);
}
}
初学者常犯错误
ctx.writeAndFlush()
ctx.channel().writeAndFlush(“hello wlfds”);
@Slf4j
public class HelloServer {
public static void main(String[] args) {
final DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();
new ServerBootstrap()
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
// 总是 head - h1 - h2 - h3 - h4 - h5 - h6- tail
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("h1",new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("------1-----------");
ByteBuf buf = (ByteBuf) msg;
System.out.println(ctx.channel().eventLoop());
log.debug(buf.toString());
super.channelRead(ctx,msg);// 数据读传递 两者二选一 ctx.fireChannelRead(msg);
}
});
pipeline.addLast("h2",new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("------2-----------");
ByteBuf buf = (ByteBuf) msg;
System.out.println(ctx.channel().eventLoop());
System.out.println(buf.toString());
super.channelRead(ctx,msg);
}
});
pipeline.addLast("h4",new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.info("------4-----------");
super.write(ctx, msg, promise);
}
});
pipeline.addLast("h3",new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("------3-----------");
ByteBuf buf = (ByteBuf) msg;
System.out.println(ctx.channel().eventLoop());
System.out.println(buf.toString());
// ctx.writeAndFlush()// 从当前往前找
ctx.channel().writeAndFlush("hello wlfds");// 从当前往后找
super.channelRead(ctx,msg);
}
});
pipeline.addLast("h5",new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.info("------5-----------");
super.write(ctx, msg, promise);
}
});
pipeline.addLast("h6",new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
log.info("------6-----------");
super.write(ctx, msg, promise);
}
});
}
}).bind(9999);
}
}
Bytebuf
自动扩容
@Slf4j
public class TestByteBuf {
public static void main(String[] args) {
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
System.out.println(buf);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 300; i++) {
sb.append("a");
}
buf.writeBytes(sb.toString().getBytes());
System.out.println(buf);
}
}
工具类
/**
* 日志打印方法
*
* @param buffer 字节缓冲对象
*/
private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
// io.netty.util.internal.StringUtil 里面静态变量 NEWLINE
.append(NEWLINE);
// io.netty.buffer.ByteBufUtil中的 静态方法
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
堆内存与直接内存
/// 默认直接内存
ByteBuf buffered = ByteBufAllocator.DEFAULT.buffer(10);
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer(10);
ByteBuf directBuffer = ByteBufAllocator.DEFAULT.directBuffer(10);
- 直接内存创建销毁代价昂贵,但是读写性能高(少一次内存复制),适合配合池化技术使用
- 直接内存对GC压力小,不受GC回收管理,但也要注意及时主动释放
池化 VS 非池化
池化的最大意义在于可以重用ByteBuf,优点
- 没有池化,每次创建新的ByteBuf
- 有了池化,重用ByteBuf
- 高并发下,池化功能更节约内存,减少内存溢出
池化默认开启
-Dio.netty.allocator.type={unpooled|pooled}
- 4.1 以后,非Android平台默认启用池化实现,Android平台启用非池化实现
- 4.1 之前,默认非池化
ByteBuf组成
扩容规则:
- 如果写入后数据未超过512,则选择下一个16的整数倍,例如写入后大小为12,则扩容后capacity是16
- 如果写入后数据大小超过512,则选择下一个2n,例如写入后大小为513,则扩容后capacity是210=1024
- 扩容不能超过max capacity 会报错
Retain & Release
由于Netty中有堆外内存的ByteBuf实现,堆外内存最好是手动来释放,而不是等GC垃圾回收
- UnpooledHeapByteBuf使用的是JVM内存,只需要等待GC回收即可
- UnpooledDirecByteBuf使用的就是直接内存,需要特殊的方法来回收内存
- PooledByteBuf和他的子类使用了池化技术,需要跟复杂的规则来回收内存
回收内存的源码实现,请关注下面方法的不同实现:
protected abstract void deallocate()
Netty 这里采用了引用计数来控制回收内存,每个ByteBuf都实现了ReferenceCounted接口
- 每个ByteBuf对象的初始化为1
- 调用release方法计数-1,如果计数为0,ByteBuf内存被回收
- 调用retain方法计数+1,表示调用者没用完之前,其他handler机使用了release也不会造成回收
- 当计数为0时,底层内存被回收,这时即使ByteBuf对象哈在,其各个方法均无法正常使用
用slice零拷贝的方式演示释放内存的最佳实践
slice 与原始的ByteBuf 公用一套内存
public static void main(String[] args) {
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10);
buf.writeBytes("abcdefghij".getBytes());
ByteBuf buf1 = buf.slice(0, 5);
buf1.retain();
ByteBuf buf2 = buf.slice(5, 5);
buf2.retain();
System.out.println(buf1.toString());
System.out.println(buf2.toString());
buf.release();
}
duplicate
截取原始ByteBuf所有内存,并且没有max capacity的限制,也是与原始的ByteBuf使用同一块底层内存,只是读写指针是独立的
copy
会将底层内存数据进行深拷贝,因此无论读写,都与原始ByteBuf无关
ComponsiteBuffer
合并两个ByteBuf
public static void main(String[] args) {
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
log(buf);
buf.writeBytes("aaa".getBytes());
ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer();
log(buf1);
buf.writeBytes("bbb".getBytes());
CompositeByteBuf compositedBuffer = ByteBufAllocator.DEFAULT.compositeBuffer();
compositedBuffer.addComponents(true,buf,buf1);
System.out.println(compositedBuffer.toString());
log(compositedBuffer);
}
ByteBuf优势
- 池化- 可以重用池中ByteBuf实例,更节约内存,减少内存溢出的可能
- 读写指针分离,不需要向ByteBuf实例一样切换读写模式
- 可以自动扩容
- 支持链式调用,使用更流畅
- 很多地方体现零拷贝,例如slice、duplicate、compositeByteBuf
简单通讯
@Slf4j
public class TestServer {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
ChannelFuture future = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.info("客户端数据 {}",buf.toString(CharsetUtil.UTF_8));
log.info("客户端地址 {}",ctx.channel().remoteAddress());
ctx.channel().writeAndFlush(buf);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
});
}
}).bind(8080);
future.channel().closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
});
}
}
@Slf4j
public class TestClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup worker = new NioEventLoopGroup();
ChannelFuture channelFuture = new Bootstrap()
.group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
ctx .writeAndFlush(Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.info("客户端读取");
log.info("服务端发送的消息 {}", buf.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
});
}
}).connect(new InetSocketAddress("localhost", 8080));
channelFuture.channel().closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
worker.shutdownGracefully();
}
});
}
}