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 种典型使用场景。

  1. 用户程序自定义的普通任务
ctx.channel().eventLoop().execute( newRunnable() { 
@Override
publicvoidrun(){ 
//...
} 
});
  1. 非当前 Reactor 线程调用 Channel 的各种方法 例如在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel
    引用,然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。最终的 Write 会提交到任务队列中后被异步消费。
  2. 用户自定义定时任务
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);
}