2. Netty核心概念
2.1 Bootstraping
Bootstraping(引导)是Netty配置的重要部分,提供了一个应用程序网络层的配置容器。Netty中有两种Bootstraping:
a) 客户端使用的Bootstrap,用于连接远程服务端。 b) 服务端使用的ServerBootstrap,用于绑定服务端发布端口。
上面两个类都是继承自AbstractBootstrap
名称 描述
group 设置 EventLoopGroup 用于处理所有的 Channel 的事件
channel channelFactory channel() 指定 Channel 的实现类。如果类没有提供一个默认的构造函数,你可以调用 channelFactory() 来指定一个工厂类被 bind() 调用。
localAddress 指定应该绑定到本地地址 Channel。如果不提供,将由操作系统创建一个随机的。或者,您可以使用 bind() 或 connect()指定localAddress
option 设置 ChannelOption 应用于 新创建 Channel 的 ChannelConfig。这些选项将被 bind 或 connect 设置在通道,这取决于哪个被首先调用。这个方法在创建管道后没有影响。所支持 ChannelOption 取决于使用的管道类型。
attr 这些选项将被 bind 或 connect 设置在通道,这取决于哪个被首先调用。这个方法在创建管道后没有影响。
handler 设置添加到 ChannelPipeline 中的 ChannelHandler 接收事件通知。
clone 创建一个当前 Bootstrap的克隆拥有原来相同的设置。
remoteAddress 设置远程地址。此外,您可以通过 connect() 指定
connect 连接到远端,返回一个 ChannelFuture, 用于通知连接操作完成
bind 将通道绑定并返回一个 ChannelFuture,用于通知绑定操作完成后,必须调用 Channel.connect() 来建立连接。
2.2 Channel
Channe是Netty网络通信的主题。Netty中的Channel接口定义了与Socket交互的操作(读、写、连接、绑定等)。负责网络通信、数据操作等。
当一个客户端连接成功后,将创建一个新的Channel。Channel同客户端进行网络连接、读写数据、关闭连接等操作。一个Channel建立成功之后,会注册到一个EventLoop上,
EventLoop用来处理该Channel中需要执行的事件。多个Channel可以注册到一个EventLoop上,Channel改变SelectionKey,会触发EventLoop调度线程,调用Channel方法进行处理。
方法名称 描述
eventLoop() 返回分配给Channel的EventLoop
pipeline() 返回分配给Channel的ChannelPipeline
isActive() 返回Channel是否激活,已激活说明与远程连接对等
localAddress() 返回已绑定的本地SocketAddress
remoteAddress() 返回已绑定的远程SocketAddress
write() 写数据到远程客户端,数据通过ChannelPipeline传输过去
flush() 刷新先前的数据
writeAndFlush(…) 一个方便的方法用户调用write(…)而后调用flush()
2.3 ByteBuf
ByteBuf是一个存储字节的容器,最大特点就是使用方便,它既有自己的读索引和写索引,方便你对整段字节缓存进行读写,也支持get/set,方便你对其中每一个字节进行读写
2.4 Codec
Netty中进行数据通信的时候,使用的是字节流。Netty中的编码/解码器,通过他你能完成字节与pojo、pojo与字节的相互转换,从而达到自定义协议的目的。当数据入站的时候,通过解码将字节转换成pojo对象。当数据出站的时候,通过编码将pojo转化成字节。
解码器Decoder 编码器Encoder 抽象编解码器Codec
2.4.1 Encoder
Encoder最重要的实现类是MessageToByteEncoder,这个类的作用是将实体T从对象转换成byte[]数组,然后再丢给剩下的ChannelOutboundHandler传给客户端
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class PojoEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf out) throws Exception {
byte[] bytes = serialize(o);//序列化
int dataLength = bytes.length; //读取消息的长度
out.writeInt(dataLength); //先将消息长度写入,也就是消息头,解决粘包和半包的问题
out.writeBytes(bytes);
}
private byte[] serialize(Object o) throws IOException {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
try {
// 序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(o);
byte[] bytes = baos.toByteArray();
return bytes;
} catch (Exception e) {
e.printStackTrace();
} finally {
oos.close();
baos.close();
}
return null;
}
}
2.4.2 Decoder
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
public class PojoDecoder extends ByteToMessageDecoder {
static int HEAD_LENGTH=4;//这个HEAD_LENGTH是我们用于表示头长度的字节数。因为Encoder在写的时候写了个int型的消息头,所以此处长度是4
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < HEAD_LENGTH) {
return;
}
in.markReaderIndex(); //我们标记一下当前的readIndex的位置
int dataLength = in.readInt(); // 读取传送过来的消息的长度,消息头。ByteBuf 的readInt()方法会让他的readIndex增加4
if (dataLength < 0) { // 我们读到的消息体长度为0,这是不应该出现的情况,这里出现这情况,关闭连接。
channelHandlerContext.close();
}
if (in.readableBytes() < dataLength) { //可读取消息体长度如果小于我们传送过来的消息长度(半包),则resetReaderIndex. 这个配合markReaderIndex使用的。把readIndex重置到mark的地方,等下次再读
in.resetReaderIndex();
return;
}
byte[] body = new byte[dataLength]; // 这时候,我们读到的长度,满足我们的要求了,把传送过来的数据,取出来吧~~
in.readBytes(body); //
Object o = unserialize(body); //将byte数据转化为我们需要的对象。
out.add(o);
}
public static Object unserialize(byte[] bytes)throws IOException {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
// 反序列化
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
bais.close();
ois.close();
}
return null;
}
}
2.5 EventLoop
EventLoop用于处理Channel的I/O操作,一个EventLoop通常会处理多个Channel事件。一个EventLoopGroup可以含有多于一个的EventLoop,可以理解EventLoopGroup是一个线程池,内部维护了一组线程。
NioEventLoop中维护了一个线程和任务队列,支持异步提交任务,线程启动时会调用NioEventLoop的run方法,执行I/O任务和非I/O任务:
I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由
processSelectedKeys 方法触发。 非 IO 任务,添加到 taskQueue 中的任务,如
register0、bind0 等任务,由 runAllTasks 方法触发。
Server端包含一个Boos和一个Worker的NioEventLoopGroup,一个NioEventLoopGroup中包含多个事件循环NioEventLoop,每个NioEventLoop包含1个Selector和1个事件循环线程。
每个Boss NioEventLoop循环执行任务包含3步: 1) 轮询Accept事件 2) 处理处理 Accept I/O 事件,与
Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker
NioEventLoop 的 Selector 上。 3) 处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用
eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。 每个Worker
NioEventLoop循环执行任务包含3步: 1) 轮询Read、Write事件 2) 处理I/O 事件,即 Read、Write
事件,在 NioSocketChannel 可读、可写事件发生时进行处理。 3) 处理任务队列中的任务,runAllTasks。其中任务队列中的 Task 有 3 种典型使用场景。
- 用户程序自定义的普通任务
ctx.channel().eventLoop().execute( newRunnable() {
@Override
publicvoidrun(){
//...
}
});
- 非当前 Reactor 线程调用 Channel 的各种方法 例如在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel
引用,然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。最终的 Write 会提交到任务队列中后被异步消费。- 用户自定义定时任务
ctx.channel().eventLoop().schedule( newRunnable() {
@Override
publicvoidrun(){
}
}, 60, TimeUnit.SECONDS);
3. 传输POJO
解码器,使用 ReplayingDecoder 就无需自己检查。若 ByteBuf
中有足够的字节,则会正常读取;若没有足够的字节则会停止解码
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
public class PojoReplayingDecoder extends ReplayingDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
int length = in.readInt();
byte[] content = new byte[length];
in.readBytes(content);
Object o = unserialize(content); //将byte数据转化为我们需要的对象。
out.add(o);
}
}
测试实体类
import java.io.Serializable;
public class User implements Serializable {
private Integer id;
private String name;
//getter/setter略
}
消息处理器:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class UserHandler extends SimpleChannelInboundHandler<User> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, User user) throws Exception {
System.out.println(user);
channelHandlerContext.fireChannelRead(user);//交给下一个handler
}
}
服务端同1.1中代码,多添加了编码解码和handler
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new PojoEncoder());
ch.pipeline().addLast(new PojoDecoder());
ch.pipeline().addLast(new UserHandler());
ch.pipeline().addLast(new DiscardHandler());//测试两个handler
}
客户端同1.2,添加编码和解码
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new PojoEncoder());
ch.pipeline().addLast(new PojoDecoder());
ch.pipeline().addLast(new StringEncoder());
}
});
Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
while (true) {//每隔两秒发一条信息
channel.writeAndFlush(new User(1,"小明"));
Thread.sleep(2000);
}