基本的线程池模式
- 从线程池的空闲线程列表中选择一个Thread,并且指派它去运行一个已提交的任务(一个Runnable的实现)
- 当任务完成时,将该Thread返回给该列表,使其可被重用。
注意虽然池化和重用线程相对于简单地为每个任务都创建和销毁线程是一种进步,但是它不能消除由上下文切换所带来的开销,其将随着线程数量的增加很快变得明显,并且在高负载下愈演愈烈。
EventLoop
EventLoop建立在java.util.concurrent包上,用来提供线程执行器。事件和任务是以先进先出的顺序执行的。这样可以保证字节内容总是按正确的顺序被处理,消除潜在的数据损害的可能性。 Netty4采用的线程模型:通过在同一个线程中处理某个给定的EventLoop中所产生的所有事件。这样消除了上下文切换带来的消耗。
任务调度
JDK的任务调度API
JDK的任务调度是基于JUC的ScheduledExecutorService接口。
//创建一个其线程池具有10个线程的ScheduledExecutorService
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
//创建一个Runnable,以供调度后稍候执行
ScheduledFuture<?> future = executor.schedule(new Runnable(){
@Override
public void run() {
System.out.println("60 seconds later");
}
},60,TimeUtil.SECONDS);
...
//调度完成,关闭ScheduledExecutorService释放资源
executor.shutdown();
EventLoop任务调度
ScheduledExecutorService的实现具有局限性,例如,事实上作为线程池管理的一部分,将会有额外的线程创建。Netty通过Channel的EventLoop实现任务调用解决了这一问题。
Netty的单元测试
EmbeddedChannel是Netty专门为改进针对ChannelHandler的单元测试而提供的
编码解*器
解*器
Netty提供了两种解器。Netty的解器实现了ChannelInboundHandler.
- 将字节解码为消息——ByteToMessageDecoder和ReplayingDecoder
- 将一种消息类型解码为另一种——MessageToMessageDecoder
代码示例
EcoServer启动器
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;
import java.net.InetSocketAddress;
/**
* @author Chensheng.Ku
* @version 创建时间:2018/12/26 15:00
*/
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) {
if(args.length != 1) {
System.out.println("Usage:" + EchoServer.class.getSimpleName()+"<port>");
}
int port = Integer.parseInt(args[0]);
new EchoServer(port).start();
}
private void start_1() {
final ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
ChannelFuture connectFuture;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Bootstrap bootstrap1 = new Bootstrap();
bootstrap1.channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data");
}
});
bootstrap1.group(ctx.channel().eventLoop());
connectFuture = bootstrap1.connect(new InetSocketAddress("www.manning.com",80));
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
if(connectFuture.isDone()) {
System.out.println("连接完成,msg="+msg.toString(CharsetUtil.UTF_8));
}
}
});
ChannelFuture future = bootstrap.bind(new InetSocketAddress(port));
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
System.out.println("Server bound");
}else {
System.out.println("Bind attempt failed");
future.cause().printStackTrace();
}
}
});
}
private void start() {
final EchoServerHandler serverHandler = new EchoServerHandler();
//创建EventLoopGroup,一个EventLoopGroup包含一个或多个EventLoop;
//一个EventLoop在它的生命周期内只和一个Thread绑定
//所有EventLoop处理的I/O时间都将在它专有的Thread上被处理
//一个Channel在它的生命周期内只注册一个EventLoop;
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建ServerBootstrap
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
//指定所使用的NIO传输Channel
.channel(NioServerSocketChannel.class)
//使用指定的端口设置套接字地址
.localAddress(new InetSocketAddress(port))
//添加一个EchoServerHandler到子Channel的ChannelPipeline
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//ChannelPipeline提供了ChannelHandler链的容器,并定义用于在该链上传播入站和出站事件流的API。当Channel被创建时,它会被自动地分配到它专属的ChannelPipeline。
//当ChannelHandler被添加到ChannelPipeline时,它会被分配一个ChannelHandlerContext,其代表了ChannelHandler和ChannelPipeline之间的绑定
socketChannel.pipeline().addLast(serverHandler);
}
});
//异步绑定服务器;调用sync()方法阻塞等待知道绑定完成
ChannelFuture future = bootstrap.bind().sync();
//获取Channel的CloseFuture,并且阻塞当前线程直到他完成
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
group.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
EchoServerHandler处理的核心部件
package com.geely.netty.chapters.chapter02.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* @author Chensheng.Ku
* @version 创建时间:2018/12/26 14:49
* 针对不同类型的事件来调用ChannelHandler
* 应用程序通过实现或者扩展ChannelHandler来挂钩到事件的生命周期,并且提供自定义的应用程序逻辑
* 在架构上,ChannelHandler有助于保持业务逻辑与网络处理代码的分离。
*
* 充当了所有处理入栈和出站数据的应用程序逻辑的容器。
* ChannelHandler的方法是由网络事件触发的。
*/
//标示一个ChannelHandler可以被多个Channel安全地共享
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received:" + in.toString(CharsetUtil.UTF_8));
//将接收到的消息写给发送者,而不是冲刷出站消息
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将未决消息冲刷到远程节点,并且关闭该Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
/**
* 如果不捕获异常,将会发生什么?
* 每个Channel都拥有一个与之相关联的ChannelPipeline,其持有一个ChannelHandler的实例链。
* 在默认的情况下,ChannelHandler会把对它的方法调用转发给链中的下一个ChannelHandler。
* 因此,如果exceptionCaught()方法没有被该链中的某处实现,那么所接受的异常将会被传递到ChannelPipeline的尾端并被记录。
* 为此,你的应用应该提供至少一个实现了exceptionCaught()方法的ChannelHandler。
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
EchoClient
package com.geely.netty.chapters.chapter02.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
/**
* @author Chensheng.Ku
* @version 创建时间:2018/12/26 16:28
*/
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public static void main(String[] args) {
if(args.length != 2) {
System.out.println("Usage:" + EchoClient.class.getSimpleName() +"<host> <port>");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
new EchoClient(host,port).start();
}
private void start() {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host,port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ChannelHandler());
}
});
ChannelFuture future = bootstrap.connect().sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
group.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
EchoClientChannelHandler
package com.geely.netty.chapters.chapter02.client;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author Chensheng.Ku
* @version 创建时间:2018/12/26 16:11
* 充当了所有处理入栈和出站数据的应用程序逻辑的容器。
* ChannelHandler的方法是由网络事件触发的。
*
* Netty使用的数据容器是ByteBuf。优点:
* 1、它可以被用户自定义的缓冲区类型扩展
* 2、通过内容的符合缓冲区类型实现了透明的零拷贝
* 3、容量可以按需增长(类似StringBuilder)
* 4、在读和写这两种模式之间不需要调用ByteBuffer的flip()方法
* 5、支持方法的链式调用
* 6、支持引用计数(实现了ReferenceCounted接口)
* 7、支持池化
*/
@io.netty.channel.ChannelHandler.Sharable
public class ChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
private ChannelHandlerContext ctx;
@Override
public void channelActive(ChannelHandlerContext ctx) {
String content = "Netty rocks!";
try {
System.out.print("请输入内容:");
content = new BufferedReader(new InputStreamReader(System.in)).readLine();
ctx.writeAndFlush(Unpooled.copiedBuffer(content, CharsetUtil.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
this.ctx = ctx;
}
/**
* 每当接收数据时,都会调用这个方法
* @param channelHandlerContext
* @param byteBuf
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("Client received:"+byteBuf.toString(CharsetUtil.UTF_8));
// System.out.print("continue(Y|N)?");
// String coutinue = new BufferedReader(new InputStreamReader(System.in)).readLine();
// switch (coutinue.toUpperCase()) {
// case "Y":
channelRegistered(this.ctx);
// this.ctx.channel().write("55555");
// break;
// case "N":
// break;
// default:
// System.out.println("不合法");
// }
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}