Netty高级利用与源码详解
粘包与半包粘包现象
粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,吸收方就可以通过边界来划分出有效的用户消息。
服务端代码
public class HelloWorldServer {
static final Logger log = LoggerFactory.getLogger(HelloWorldServer.class);
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("connected {}", ctx.channel());
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.debug("disconnect {}", ctx.channel());
super.channelInactive(ctx);
}
});
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080);
log.debug("{} binding...", channelFuture.channel());
channelFuture.sync();
log.debug("{} bound...", channelFuture.channel());
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.debug("stoped");
}
}
public static void main(String[] args) {
new HelloWorldServer().start();
}
}客户端代码盼望发送 10 个消息,每个消息是 16 字节
public class HelloWorldClient {
static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(worker);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
log.debug("connetted...");
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
//会在连接channel建立成功后,会触发Active事件
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("sending...");
Random r = new Random();
char c = 'a';
for (int i = 0; i < 10; i++) {
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
ctx.writeAndFlush(buffer);
}
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}服务器端的某次输出,可以看到一次就吸收了 160 个字节,而期望的是一次16字节,分 10 次吸收。这就出现了粘包现象
08:24:46 c.i.n.HelloWorldServer - binding...
08:24:46 c.i.n.HelloWorldServer - bound...
08:24:55 i.n.h.l.LoggingHandler - REGISTERED
08:24:55 i.n.h.l.LoggingHandler - ACTIVE
08:24:55 c.i.n.HelloWorldServer - connected
08:24:55 i.n.h.l.LoggingHandler - READ: 160B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000020| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
+--------+-------------------------------------------------+----------------+
08:24:55 i.n.h.l.LoggingHandler - READ COMPLETE半包现象
半包是指 吸收端只收到了部门数据,而非完备的数据的情况
客户端代码盼望发送 1 个消息,这个消息是 160 字节,代码改为
ByteBuf buffer = ctx.alloc().buffer();
for (int i = 0; i < 10; i++) {
buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
}
ctx.writeAndFlush(buffer);为现象显着,服务端修改一下吸收缓冲区,其它代码不变
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);服务器端的某次输出,可以看到吸收的消息被分为两节,如 第一次 20 字节,第二次 140 字节
08:43:49 c.i.n.HelloWorldServer - binding...
08:43:49 c.i.n.HelloWorldServer - bound...
08:44:23 i.n.h.l.LoggingHandler - REGISTERED
08:44:23 i.n.h.l.LoggingHandler - ACTIVE
08:44:23 c.i.n.HelloWorldServer - connected
08:44:24 i.n.h.l.LoggingHandler - READ: 20B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 00 01 02 03 |.... |
+--------+-------------------------------------------------+----------------+
08:44:24 i.n.h.l.LoggingHandler - READ COMPLETE
08:44:24 i.n.h.l.LoggingHandler - READ: 140B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000020| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000030| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000040| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000050| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000060| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000070| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
|00000080| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |............ |
+--------+-------------------------------------------------+----------------+
08:44:24 i.n.h.l.LoggingHandler - READ COMPLETE留意:serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层吸收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍
现象分析
这里出现的粘包半包问题,并非是JavaNIO或Netty的问题,本质是TCP是流失协议,消息无边界。
粘包:
[*]现象,发送 abc def,吸收 abcdef
[*]原因
[*]应用层:吸收方 ByteBuf 设置太大(Netty 默认 1024)
[*]滑动窗口:假设发送方 256 bytes 表现一个完备报文,但由于吸收方处置惩罚不及时且窗口大小足够大,这 256 bytes 字节就会缓冲在吸收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包
[*]Nagle 算法:会造成粘包
半包
[*]现象,发送 abcdef,吸收 abc def
[*]原因
[*]应用层:吸收方 ByteBuf 小于实际发送数据量
[*]滑动窗口:假设吸收方的窗口只剩了 128 bytes,发送方的报文大小是 256 bytes,这时放不下了,只能先发送前 128 bytes,等待 ack 后才能发送剩余部门,这就造成了半包
[*]MSS 限制:当发送的数据凌驾 MSS 限制后,会将数据切分发送,就会造成半包
解决方案
接下来看下Netty如何解决以上问题的:
[*]短链接,发一个包建立一次连接,这样连接建立到连接断开之间就是消息的边界,缺点效率太低
[*]每一条消息接纳固定长度,缺点浪费空间
[*]每一条消息接纳分隔符,例如 \n,缺点需要转义
[*]每一条消息分为 head 和 body,head 中包罗 body 的长度
方法1:短链接(极不保举)
以解决粘包为例
public class HelloWorldClient {
static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
public static void main(String[] args) {
// 分 10 次发送
for (int i = 0; i < 10; i++) {
send();
}
}
private static void send() {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(worker);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
log.debug("conneted...");
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("sending...");
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
ctx.writeAndFlush(buffer);
// 发完即关
ctx.close();
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}输出,略
半包用这种办法照旧欠好解决,因为吸收方的缓冲区大小是有限的
方法2:固定长度
让所有数据包长度固定(假设长度为 8 字节),服务器端参加
ch.pipeline().addLast(new FixedLengthFrameDecoder(8));客户端测试代码,留意, 接纳这种方法后,客户端什么时候 flush 都可以
public class HelloWorldClient {
static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(worker);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
log.debug("connetted...");
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("sending...");
// 发送内容随机的数据包
Random r = new Random();
char c = 'a';
ByteBuf buffer = ctx.alloc().buffer();
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte;
for (int j = 0; j < r.nextInt(8); j++) {
bytes = (byte) c;
}
c++;
buffer.writeBytes(bytes);
}
ctx.writeAndFlush(buffer);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("192.168.0.103", 9090).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}客户端输出
12:07:00 c.i.n.HelloWorldClient - connetted...
12:07:00 i.n.h.l.LoggingHandler - REGISTERED
12:07:00 i.n.h.l.LoggingHandler - CONNECT: /192.168.0.103:9090
12:07:00 i.n.h.l.LoggingHandler - ACTIVE
12:07:00 c.i.n.HelloWorldClient - sending...
12:07:00 i.n.h.l.LoggingHandler - WRITE: 80B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 00 00 00 00 62 00 00 00 00 00 00 00 |aaaa....b.......|
|00000010| 63 63 00 00 00 00 00 00 64 00 00 00 00 00 00 00 |cc......d.......|
|00000020| 00 00 00 00 00 00 00 00 66 66 66 66 00 00 00 00 |........ffff....|
|00000030| 67 67 67 00 00 00 00 00 68 00 00 00 00 00 00 00 |ggg.....h.......|
|00000040| 69 69 69 69 69 00 00 00 6a 6a 6a 6a 00 00 00 00 |iiiii...jjjj....|
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - FLUSH服务端输出
12:06:51 c.i.n.HelloWorldServer - binding...
12:06:51 c.i.n.HelloWorldServer - bound...
12:07:00 i.n.h.l.LoggingHandler - REGISTERED
12:07:00 i.n.h.l.LoggingHandler - ACTIVE
12:07:00 c.i.n.HelloWorldServer - connected
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 00 00 00 00 |aaaa.... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 62 00 00 00 00 00 00 00 |b....... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 63 63 00 00 00 00 00 00 |cc...... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 64 00 00 00 00 00 00 00 |d....... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 00 00 00 00 00 |........ |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 66 66 66 66 00 00 00 00 |ffff.... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 67 67 67 00 00 00 00 00 |ggg..... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 68 00 00 00 00 00 00 00 |h....... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 69 69 69 69 69 00 00 00 |iiiii... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ: 8B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 6a 6a 6a 6a 00 00 00 00 |jjjj.... |
+--------+-------------------------------------------------+----------------+
12:07:00 i.n.h.l.LoggingHandler - READ COMPLETE缺点是,数据包的大小欠好把握
[*]长度定的太大,浪费
[*]长度定的太小,对某些数据包又显得不够
方法3:固定分隔符
服务端参加,默认以 \n 或 \r\n 作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));客户端在每条消息之后,参加 \n 分隔符
public class HelloWorldClient {
static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(worker);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
log.debug("connetted...");
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("sending...");
Random r = new Random();
char c = 'a';
ByteBuf buffer = ctx.alloc().buffer();
for (int i = 0; i < 10; i++) {
for (int j = 1; j <= r.nextInt(16)+1; j++) {
buffer.writeByte((byte) c);
}
buffer.writeByte(10);
c++;
}
ctx.writeAndFlush(buffer);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("192.168.0.103", 9090).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}增长设置类和设置文件
14:08:18 c.i.n.HelloWorldClient - connetted...
14:08:18 i.n.h.l.LoggingHandler - REGISTERED
14:08:18 i.n.h.l.LoggingHandler - CONNECT: /192.168.0.103:9090
14:08:18 i.n.h.l.LoggingHandler - ACTIVE
14:08:18 c.i.n.HelloWorldClient - sending...
14:08:18 i.n.h.l.LoggingHandler - WRITE: 60B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 61 0a 62 62 62 0a 63 63 63 0a 64 64 0a 65 65 65 |a.bbb.ccc.dd.eee|
|00000010| 65 65 65 65 65 65 65 0a 66 66 0a 67 67 67 67 67 |eeeeeee.ff.ggggg|
|00000020| 67 67 0a 68 68 68 68 0a 69 69 69 69 69 69 69 0a |gg.hhhh.iiiiiii.|
|00000030| 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 0a |jjjjjjjjjjj. |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - FLUSH设置文件
14:08:18 c.i.n.HelloWorldServer - connected
14:08:18 i.n.h.l.LoggingHandler - READ: 1B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 61 |a |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 3B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62 |bbb |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 3B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 63 63 63 |ccc |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 2B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 64 64 |dd |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 10B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 65 65 65 65 65 65 65 65 65 65 |eeeeeeeeee |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 2B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 66 66 |ff |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 7B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 67 67 67 67 67 67 67 |ggggggg |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 4B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 68 68 68 68 |hhhh |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 7B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 69 69 69 69 69 69 69 |iiiiiii |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ: 11B
+-------------------------------------------------+
|0123456789abcdef |
+--------+-------------------------------------------------+----------------+
|00000000| 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a |jjjjjjjjjjj |
+--------+-------------------------------------------------+----------------+
14:08:18 i.n.h.l.LoggingHandler - READ COMPLETE修改编解码器
/** * 必须和 LengthFieldBasedFrameDecoder 一起利用,确保接到的 ByteBuf 消息是完备的 */public class MessageCodecSharable extends MessageToMessageCodec { @Override public void encode(ChannelHandlerContext ctx, Message msg, List outList) throws Exception { ByteBuf out = ctx.alloc().buffer(); // 1. 4 字节的魔数 out.writeBytes(new byte[]{1, 2, 3, 4}); // 2. 1 字节的版本, out.writeByte(1); // 3. 1 字节的序列化方式 jdk 0 , json 1 out.writeByte(Config.getSerializerAlgorithm().ordinal()); // 4. 1 字节的指令类型 out.writeByte(msg.getMessageType()); // 5. 4 个字节 out.writeInt(msg.getSequenceId()); // 无意义,对齐填充 out.writeByte(0xff); // 6. 获取内容的字节数组 byte[] bytes = Config.getSerializerAlgorithm().serialize(msg); // 7. 长度 out.writeInt(bytes.length); // 8. 写入内容 out.writeBytes(bytes); outList.add(out); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { int magicNum = in.readInt(); byte version = in.readByte(); byte serializerAlgorithm = in.readByte(); // 0 或 1 byte messageType = in.readByte(); // 0,1,2... int sequenceId = in.readInt(); in.readByte(); int length = in.readInt(); byte[] bytes = new byte; in.readBytes(bytes, 0, length); // 找到反序列化算法 Serializer.Algorithm algorithm = Serializer.Algorithm.values(); // 确定具体消息类型 Class, Object> options = options0(); synchronized (options) { setChannelOptions(channel, options, logger); } final Map, Object>[] currentChildOptions; final Entry
页:
[1]