本文是读书笔记,读的《Netty实战》
一、Netty 的核心组件
- Channel;
- 回调;
- Future;
- 事件和 ChannelHandler。
1.Channel
- EmbeddedChannel;
- LocalServerChannel;
- NioDatagramChannel;
- NioSctpChannel;
- NioSocketChannel。
2.回调
3.Future和ChannelFuture
4.事件和 ChannelHandler
4.1 可能由入站数据或者相关的状态更改而触发的事件包括:
- 连接已被激活或者连接失活;
- 数据读取
- 用户事件
- 错误事件
- 打开或者关闭到远程节点的连接;
- 将数据写到或者冲刷到套接字。
5.EventLoop
- 注册感兴趣的事件;
- 将事件派发给 ChannelHandler;
- 安排进一步的动作。
Channel、EventLoop、Thread 以及 EventLoopGroup 之间的关系
这些关系是:
- 一个 EventLoopGroup 包含一个或者多个 EventLoop;
- 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
- 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
- 一个 Channel 在它的生命周期内只注册于一个 EventLoop;
- 一个 EventLoop 可能会被分配给一个或多个 Channel。
注意,在这种设计中,一个给定 Channel 的 I/O 操作都是由相同的 Thread 执行的,实际
6.ChannelPipeline 接口
- 一个ChannelInitializer的实现被注册到了ServerBootstrap中 ①;
- 当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler;
- ChannelInitializer 将它自己从 ChannelPipeline 中移除。
在 Netty 中,有两种发送消息的方式。
这种方式将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动。
7、Bootstrap和EventLoopGroup
- 1.用于客户端(简单地称为 Bootstrap)
- 2.(ServerBootstrap)用于服务器。
二、用NETTY实现ECHO的代码
1.服务端
package com.sid.echo.server;
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
import java.net.InetSocketAddress;
/**
* 绑定到服务器将在其上监听并接受传入连接请求的端口;
* 配置 Channel,以将有关的入站消息通知给 EchoServerHandler 实例。
* */
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println(
"Usage: " + EchoServer.class.getSimpleName() + " <port>");
}
int port = Integer.parseInt(args[0]);
new EchoServer(port).start();
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(serverHandler);
}
});
//异步地绑定服务器;调用 sync()方法阻塞;等待直到绑定完成
ChannelFuture f = b.bind().sync();
//获取 Channel 的CloseFuture,并且阻塞当前线
f.channel().closeFuture().sync();
} finally {
//关闭 EventLoopGroup,释放所有的资源,包括所有被创建的线程
group.shutdownGracefully().sync();
}
}
}
package com.sid.echo.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
/**
* 对于每个传入的消息都要调用;
* */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
/**
* —通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息;
* */
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
/**
* —在读取操作期间,有异常抛出时会调用
*
*如果不捕获异常,会发生什么呢?
* 每个 Channel 都拥有一个与之相关联的 ChannelPipeline,其持有一个 ChannelHandler 的
* 实例链。在默认的情况下,ChannelHandler 会把对它的方法的调用转发给链中的下一个 ChannelHandler。因此,如果 exceptionCaught()方法没有被该链中的某处实现,那么所接收的异常将会被
* 传递到 ChannelPipeline 的尾端并被记录。为此,你的应用程序应该提供至少有一个实现了
* exceptionCaught()方法的 ChannelHandler。
* */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
2.客户端
package com.sid.echo.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;
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new EchoClientHandler());
}
});
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println(
"Usage: " + EchoClient.class.getSimpleName() + " <host> <port>");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
new EchoClient(host, port).start();
}
}
package com.sid.echo.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;
/**
* SimpleChannelInboundHandler 与 ChannelInboundHandler
* 你可能会想:为什么我们在客户端使用的是 SimpleChannelInboundHandler,而不是在 EchoServerHandler 中所使用的 ChannelInboundHandlerAdapter 呢?这和两个因素的相互作用有
* 关:业务逻辑如何处理消息以及 Netty 如何管理资源。
* 在客户端,当 channelRead0()方法完成时,你已经有了传入消息,并且已经处理完它了。当该方
* 法返回时,SimpleChannelInboundHandler 负责释放指向保存该消息的 ByteBuf 的内存引用。
* 在 EchoServerHandler 中,你仍然需要将传入消息回送给发送者,而 write()操作是异步的,直
* 到 channelRead()方法返回后可能仍然没有完成(如代码清单 2-1 所示)。为此,EchoServerHandler
* 扩展了 ChannelInboundHandlerAdapter,其在这个时间点上不会释放消息。
* 消息在 EchoServerHandler 的 channelReadComplete()方法中,当 writeAndFlush()方
* 法被调用时被释放
* */
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 每当接收数据时,都会调用这个方法
*
* 由服务器发送的消息可能会被分块接收。也就是说,如果服务器发送了 5 字节,那么不
* 能保证这 5 字节会被一次性接收。即使是对于这么少量的数据,channelRead0()方法也可能
* 会被调用两次,第一次使用一个持有 3 字节的 ByteBuf(Netty 的字节容器),第二次使用一个
* 持有 2 字节的 ByteBuf。作为一个面向流的协议,TCP 保证了字节数组将会按照服务器发送它
* 们的顺序被接收。
* */
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
System.out.println(
"Client received: " + in.toString(CharsetUtil.UTF_8));
}
/**
* 其将在一个连接建立时被调用
* */
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",
CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
3.SimpleChannelInboundHandler 与 ChannelInboundHandler
为什么我们在客户端使用的是 SimpleChannelInboundHandler,而不是在 EchoServerHandler 中所使用的 ChannelInboundHandlerAdapter 呢?
这和两个因素的相互作用有关:业务逻辑如何处理消息以及 Netty 如何管理资源。
在客户端,当 channelRead0()方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler 负责释放指向保存该消息的 ByteBuf 的内存引用。
在 EchoServerHandler 中,你仍然需要将传入消息回送给发送者,而 write()操作是异步的,直到 channelRead()方法返回后可能仍然没有完成。为此,EchoServerHandler
扩展了 ChannelInboundHandlerAdapter,其在这个时间点上不会释放消息。
消息在 EchoServerHandler 的 channelReadComplete()方法中,当 writeAndFlush()方法被调用时被释放