2019独角兽企业重金招聘Python工程师标准>>>
1、NioEventLoopGroup
一个Netty服务端启动时,通常会有两个NioEventLoopGroup:一个boss,一个worker。第一个NioEventLoopGroup正常只需要一个EventLoop,主要负责客户端的连接请求,然后打开一个Channel,交给第二个NioEventLoopGroup中的一个EventLoop来处理这个Channel上的所有读写事件。一个Channel只会被一个EventLoop处理,而一个EventLoop可能会被分配给多个Channel。
2、ChannelFuture 接口
由于Netty中的所有I/O操作都是异步的,因此Netty为了解决调用者如何获取异步操作结果的问题而专门设计了ChannelFuture接口.
可以调用ChannelFuture里的方法获取channel,Channel与ChannelFuture可以说形影不离的.
3、Channel(通道)
可以理解为该类对socket进行了封装,提供了数据的读取和写入功能。
4、ChannelHandler
Netty 定义了下面两个重要的ChannelHandler 子接口:
ChannelInboundHandler(入站handler) —— channel上有消息可读时,netty就会将该消息交给入站处理器处理。可以有过个处理器,形成一个链条。
内部方法:
当某个 ChannelInboundHandler 的实现重写 channelRead()方法时,可以显式地释放与池化的 ByteBuf 实例相关的内存。Netty 为此提供了一个实用方法 ReferenceCountUtil.release();比如
但是以这种方式管理资源可能很繁琐,一个更加简单的方式是使用SimpleChannelInboundHandler 会自动释放资源。如下:
ChannelOutboundHandler(出站handler) —— channel写出数据时,会经过出站处理器的处理后,再发送到网络上。可以有多个处理器,形成一个链条。
内部方法:
ChannelPromise与ChannelFuture:ChannelOutboundHandler中的大部分方法都需要一个ChannelPromise参数,以便在操作完成时得到通知。ChannelPromise是ChannelFuture的一个子类,其定义了一些可写的方法,如setSuccess()和setFailure(),从而使ChannelFuture不可变。
注意:
如果一个消息被消费或者丢弃了,并且没有传递给 ChannelPipeline 中的下一个ChannelOutboundHandler,那么用户就有责任调用 ReferenceCountUtil.release()。
比如:
如果消息到达了实际的传输层,那么当它被写入时或者 Channel 关闭时,都将被自动释放。
5、ChannelPipeline
Netty会把出站Handler和入站Handler放到一个Pipeline中,同属一个方向的Handler则是有顺序的,因为上一个Handler处理的结果往往是下一个Handler的要求的输入。
6、ChannelHandlerContext
ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext。ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在同一个 ChannelPipeline 中的其他 ChannelHandler之间的交互。
内部方法:
消息向下传递ctx.fireChannelRead(msg); 这个方法可能你可能会用到,其次就是write相关方法比较常用。
7、 实战多个入站和出站处理器
7.1 服务端
Message对象:
@Data
public class Message implements Serializable{
//消息内容
private String content;
//消息时间戳
long time;
}
入站处理器1:将Byte转为Messag对象
/**
* 将ByteBuf数据 转为对象
*/
public class EchoServerInHandler1 extends ChannelInboundHandlerAdapter {
/**
* 服务端读取到网络数据后的处理
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
//ByteBuf为netty实现的缓冲区
ByteBuf in = (ByteBuf)msg;
String msgStr = in.toString(CharsetUtil.UTF_8);
System.out.println("EchoServerInHandler1 处理:" + msgStr);
Message message = JSON.parseObject(msgStr,new TypeReference(){});
ReferenceCountUtil.release(msg);//通过使用 ReferenceCountUtil.realse(...)方法释放资源
ctx.fireChannelRead(message);
}
}
入站处理器2:业务处理
public class EchoServerInHandler2 extends SimpleChannelInboundHandler {
/**
* 服务端读取到网络数据后的处理
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead0(ChannelHandlerContext ctx, Message msg)
throws Exception {
System.out.println("EchoServerInHandler2 收到消息:" + msg);
Message message = new Message();
message.setContent("success");
ctx.writeAndFlush(message);
}
}
出站处理器1:将Message对象转为字节发送到网络
public class EchoServerOutHandler1 extends ChannelOutboundHandlerAdapter{
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("EchoServerOutHandler1 处理:"+msg);
Message message = (Message)msg;
String messageJson = JSON.toJSONString(message);
ByteBuf byteBuf = Unpooled.copiedBuffer(messageJson, CharsetUtil.UTF_8);
ctx.write(byteBuf, promise);
}
}
出站处理器2:给Message对象加上时间戳
public class EchoServerOutHandler2 extends ChannelOutboundHandlerAdapter{
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("EchoServerOutHandler2 处理:"+msg);
Message message = (Message) msg;
message.setTime(System.currentTimeMillis());
ctx.write(msg, promise);
}
}
在pipeline上添加入站和出站处理器
public class EchoServer {
private int port;
private EventLoopGroup bossGroup;
private EventLoopGroup workGroup;
private ServerBootstrap b;
public EchoServer(int port) {
this.port = port;
//第一个线程组是用于接收Client连接的
bossGroup = new NioEventLoopGroup(1);
//第二个线程组是用于消息的读写操作
workGroup = new NioEventLoopGroup(2);
//服务端辅助启动类
b = new ServerBootstrap();
b.group(bossGroup, workGroup)
//需要指定使用NioServerSocketChannel这种类型的通道
.channel(NioServerSocketChannel.class)
//向ChannelPipeline里添加业务处理handler
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//出站处理器 先注册后执行
ch.pipeline().addLast(new EchoServerOutHandler1());//out 1
ch.pipeline().addLast(new EchoServerOutHandler2());//out 2
//入站处理器 先注册先执行
ch.pipeline().addLast(new EchoServerInHandler1());//in1
ch.pipeline().addLast(new EchoServerInHandler2());//in2
}
});
}
/**
* 启动
* @throws InterruptedException
*/
public void start() throws InterruptedException {
try {
//绑定到端口,阻塞等待直到完成
b.bind(this.port).sync();
System.out.println("服务器启动成功");
} finally {
}
}
/**
* 资源优雅释放
*/
public void close() {
try {
if (bossGroup != null)
bossGroup.shutdownGracefully().sync();
if (workGroup != null)
workGroup.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException, IOException {
int port = 9999;
EchoServer echoServer = new EchoServer(port);
try {
echoServer.start();
//防止主程序退出
System.in.read();
} finally {
echoServer.close();
}
}
}
需要注意处理器的顺序:
先说最基本的,读入数据,执行顺序和注册顺序一致 in1 --> in2 ,他们之间通过 ctx.fireChannelRead(msg);进行传递。
从EchoServerInHandler1开始执行到EchoServerInHandler2,逻辑处理,进行数据发送返回, 通过ctx.writeAndFlush()就完成从in -->out的转换。
ctx.writeAndFlush()从当前节点往前查找out类handler,所以就会以 out2----》out1 这样一种方式执行。
假如下面这种顺序, 出站处理器放到后面,这时调用ctx.writeAndFlush()往前找out类型的处理器就会找不到。这种情况要想执行outhandler的处理,应该执行ctx.channel().writeAndFlush();这是从链表结尾开始往前查找out类handler,这就是两种writeAndFlush的区别
7.2 客户端代码
public class EchoClient {
private final int port;
private final String host;
private Channel channel;
private EventLoopGroup group;
private Bootstrap b;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
//客户端启动辅助类
b = new Bootstrap();
//构建线程组 处理读写
group = new NioEventLoopGroup();
b.group(group)
//指明使用NIO进行网络通讯
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//出站处理器 先注册后执行
ch.pipeline().addLast(new EchoClientOutHandler1());
ch.pipeline().addLast(new EchoClientOutHandler2());
//入站处理器 先注册先执行
ch.pipeline().addLast(new EchoClientInHandler1());
ch.pipeline().addLast(new EchoClientInHandler2());
}
});
}
public void start() {
try {
//连接到远程节点,阻塞等待直到连接完成
ChannelFuture f = b.connect(new InetSocketAddress(host, port)).sync();
//同步获取channel(通道,实际上是对socket的封装支持读取和写入)
channel = f.sync().channel();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 发送消息
* @param msg
* @return
*/
public boolean send(String msg) {
Message message = new Message();
message.setContent(msg);
channel.writeAndFlush(message);
return true;
}
/**
* 关闭释放资源
*/
public void close(){
try {
if (group != null)
group.shutdownGracefully().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
EchoClient client = new EchoClient("127.0.0.1", 9999);
try {
client.start();
Scanner scanner = new Scanner(System.in);
while (true) {
String msg = scanner.next();
client.send(msg);
}
}finally {
client.close();
}
}
}
public class EchoClientInHandler1 extends ChannelInboundHandlerAdapter{
/**读取数据
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
//ByteBuf为netty实现的缓冲区
ByteBuf in = (ByteBuf)msg;
String msgStr = in.toString(CharsetUtil.UTF_8);
System.out.println("EchoClientInHandler1 处理:" + msgStr);
Message message = JSON.parseObject(msgStr,new TypeReference(){});
ctx.fireChannelRead(message);
}
/**
* 当Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("EchoClientInHandler1 channelActive");
}
/**
* exceptionCaught()事件处理方法当出现Throwable对象才会被调用
* 即当Netty由于IO错误或者处理器在处理事件时抛出的异常时
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
public class EchoClientInHandler2 extends SimpleChannelInboundHandler {
/**
* 客户端读取到数据
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead0(ChannelHandlerContext ctx, Message msg)
throws Exception {
System.out.println("EchoClientInHandler2 handler:" + msg);
}
}
/**
* 该处理器将对象转为字节 发送到网络上
*/
public class EchoClientOutHandler1 extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("EchoClientOutHandler1 处理:"+msg);
Message message = (Message)msg;
String messageJson = JSON.toJSONString(message);
ByteBuf byteBuf = Unpooled.copiedBuffer(messageJson, CharsetUtil.UTF_8);
ctx.write(byteBuf, promise);
}
}
/**
* 该处理器给message加上时间戳
*/
public class EchoClientOutHandler2 extends ChannelOutboundHandlerAdapter{
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("EchoClientOutHandler2 处理:"+msg);
Message message = (Message) msg;
message.setTime(System.currentTimeMillis());
ctx.write(msg, promise);
}
}
启动服务端和客户端,然后发送消息