Netty学习笔记四
四. Netty 进阶
1. 粘包半包
1.1 粘包
server端
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
/**
* @program: netty-study
* @description: 粘包半包示例代码
* @author: SunYang
* @create: 2021-08-22 14:13
**/
@Slf4j(topic = "c.Demo")
public class NianBanServerDemo {
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
// ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
// @Override
// // 会在连接 channel建立成功后,会触发active事件
// public void channelActive(ChannelHandlerContext ctx) throws Exception {
// log.debug("connected {}", ctx.channel());
// super.channelActive(ctx);
// }
// });
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.debug("stoped");
}
}
public static void main(String[] args) {
new NianBanServerDemo().start();
}
}
client端
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
/**
* @program: netty-study
* @description: 粘包半包客户端
* @author: SunYang
* @create: 2021-08-22 15:23
**/
@Slf4j(topic = "c.Demo")
public class NianBanClientDemo {
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
// 会在连接 channel建立成功后,会触发active事件
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10; i++) {
ByteBuf byteBuf = ctx.alloc().buffer(16);
byteBuf.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
ctx.writeAndFlush(byteBuf);
}
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}
输出
15:36:04.193 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x038b6d37, L:/127.0.0.1:8080 - R:/127.0.0.1:63404] REGISTERED
15:36:04.199 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x038b6d37, L:/127.0.0.1:8080 - R:/127.0.0.1:63404] ACTIVE
15:36:04.265 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x038b6d37, L:/127.0.0.1:8080 - R:/127.0.0.1:63404] READ: 160B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|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 |................|
+--------+-------------------------------------------------+----------------+
15:36:04.268 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x038b6d37, L:/127.0.0.1:8080 - R:/127.0.0.1:63404] READ COMPLETE
正常应该是分10次发送,每次16B,但是现在一次发了160B 所以发生了粘包现象
1.2 半包
server端
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
/**
* @program: netty-study
* @description: 粘包半包示例代码
* @author: SunYang
* @create: 2021-08-22 14:13
**/
@Slf4j(topic = "c.Demo")
public class NianBanServerDemo {
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
// 设置channel接收缓冲区大小
serverBootstrap.option(EpollChannelOption.SO_RCVBUF, 10);
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
// ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
// @Override
// // 会在连接 channel建立成功后,会触发active事件
// public void channelActive(ChannelHandlerContext ctx) throws Exception {
// log.debug("connected {}", ctx.channel());
// super.channelActive(ctx);
// }
// });
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.debug("stoped");
}
}
public static void main(String[] args) {
new NianBanServerDemo().start();
}
}
client不变
输出
15:42:06.166 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] REGISTERED
15:42:06.176 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] ACTIVE
15:42:06.235 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ: 36B // 拆包
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|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 |.... |
+--------+-------------------------------------------------+----------------+
15:42:06.239 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ COMPLETE
15:42:06.240 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ: 40B // 粘包
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|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 |........ |
+--------+-------------------------------------------------+----------------+
15:42:06.241 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ COMPLETE
15:42:06.242 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ: 40B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
|00000010| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
|00000020| 0c 0d 0e 0f 00 01 02 03 |........ |
+--------+-------------------------------------------------+----------------+
15:42:06.243 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ COMPLETE
15:42:06.245 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ: 40B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|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 |........ |
+--------+-------------------------------------------------+----------------+
15:42:06.246 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ COMPLETE
15:42:06.246 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ: 4B // 半包现象
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 0c 0d 0e 0f |.... |
+--------+-------------------------------------------------+----------------+
15:42:06.247 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0xaba5ce7a, L:/127.0.0.1:8080 - R:/127.0.0.1:56104] READ COMPLETE
注意
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍
1.3 滑动窗口
滑动窗口
TCP 以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差
为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值
窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用
- 图中深色的部分即要发送的数据,高亮的部分即窗口
- 窗口内的数据才允许被发送,当应答未到达前,窗口必须停止滑动
- 如果 1001~2000 这个段的数据 ack 回来了,窗口就可以向前滑动
- 接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收
MSS 限制
链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同,例如
以太网的 MTU 是 1500
FDDI(光纤分布式数据接口)的 MTU 是 4352
本地回环地址的 MTU 是 65535 - 本地测试不走网卡
MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数
ipv4 tcp 头占用 20 bytes,ip 头占用 20 bytes,因此以太网 MSS 的值为 1500 - 40 = 1460
TCP 在传递大量数据时,会按照 MSS 大小将数据进行分割发送
MSS 的值在三次握手时通知对方自己 MSS 的值,然后在两者之间选择一个小值作为 MSS
Nagle 算法
- 即使发送一个字节,也需要加入 tcp 头和 ip 头,也就是总字节数会使用 41 bytes,非常不经济。因此为了提高网络利用率,tcp 希望尽可能发送足够大的数据,这就是 Nagle 算法产生的缘由
- 该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送
- 如果 SO_SNDBUF 的数据达到 MSS,则需要发送
- 如果 SO_SNDBUF 中含有 FIN(表示需要连接关闭)这时将剩余数据发送,再关闭
- 如果 TCP_NODELAY = true,则需要发送
- 已发送的数据都收到 ack 时,则需要发送
- 上述条件不满足,但发生超时(一般为 200ms)则需要发送
- 除上述情况,延迟发送
如果接收方的滑动窗口(接收缓冲区)没有地方了,接收到一半,那么就会出现半包现象。
如果接收方的滑动窗口空闲很多,那么就可能出现粘包现象。
1.4 现象分析
粘包
-
现象:发送abc 在发送def , 接收方收收到abcdef
-
原因
-
应用层: 接收方Netty的ByteBuf设置太大(自己创建的ByteBuf),
-
也可以用
-
serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16,16));// 不设置这个默认大小是默认1024;
-
-
TCP 层:滑动窗口(channel接收缓冲区大小):假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这 256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包
-
serverBootstrap.option(EpollChannelOption.SO_RCVBUF, 10);
-
-
TCP层:nagle算法:会造成粘包
-
半包
- 现象:发送abcdef, 接收abc 然后接收def
- 原因:
- 应用层:接收方ByteBuf小于实际发送数据量
- TCP层:滑动窗口:假设接收方的窗口只剩下了128bytes,发送方的报文大小是256bytes,这时放不下了,只能先发送前128bytes,等待ack确认后才能发送剩余部分,这就造成了半包
- 链路层(网卡(内核)缓冲区):MSS限制:当发送的数据超过MSS限制后,会将数据切分发送,就会造成半包(例如请求百度首页,就会给你分两次1500左右大小的数据包发送过来。)
本质是因为TCP是流式协议,消息无边界
滑动窗口的大小与套接字缓存区会在一定程度上影响并发连接的数据,每个TCP连接都会为维护TCP滑动窗口而消耗内存,这个窗口会根据服务器的处理速度收缩或扩张。
这里想说的是内核缓冲区和滑动窗口之间的区别联系:
整个数据的流程中,首先网卡接收到的数据存放到内核缓冲区内,然后内核缓冲区存放的数据根据TCP信息将数据移动到具体的某一个TCP连接上的接收缓冲区内,也就是接收滑动窗口内,然后应用程序从TCP的接受缓冲区内读取数据,如果应用程序一直不读取,那么滑动窗口就会变小,直至为0.
如果网卡处理数据的速度比内核处理数据的速度慢,那么内核会有一个队列来保存这些数据,这个队列的大小就是由参数netdev_max_backlog决定的
对于发送数据来说,应用程序将数据拷贝到各自TCP发送缓冲区内(也就是发送滑动窗口),然后系统的所有TCP套接字上发送缓冲区(也就是发送滑动窗口)内的数据都将数据拷贝到内核发送缓冲区内,然后内核将内核缓冲区的数据经过网卡发送出去。
TCP的发送/接受缓冲区(也就是发送/接受滑动窗口),是针对某一个具体的TCP连接来说的,每一个TCP连接都会有相应的滑动窗口,但是内核的发送/接受缓冲区是针对整个系统的,里面存放着整个系统的所有TCP连接的接收/发送的数据。
1.5 解决方法一-短链接
可以解决粘包问题,但是不能解决半包问题
server
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
/**
* @program: netty-study
* @description: 粘包半包示例代码
* @author: SunYang
* @create: 2021-08-22 14:13
**/
@Slf4j(topic = "c.Demo")
public class NianBanServerDemo {
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
// 设置channel接收缓冲区大小 影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍
// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
// 等于我们自己创建的那种ByteBuf,但是这里这个是一个可以根据接收数据大小自动调整的
// serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16,16));
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
// ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
// @Override
// // 会在连接 channel建立成功后,会触发active事件
// public void channelActive(ChannelHandlerContext ctx) throws Exception {
// log.debug("connected {}", ctx.channel());
// super.channelActive(ctx);
// }
// });
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.debug("stoped");
}
}
public static void main(String[] args) {
new NianBanServerDemo().start();
}
}
client
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
/**
* @program: netty-study
* @description: 解决粘包半包方法一之短连接-客户端
* @author: SunYang
* @create: 2021-08-22 17:03
**/
@Slf4j(topic = "c.Demo")
public class ShortConnectClient {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
send();
}
System.out.println("结束");
}
private static void send() {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
// 会在连接 channel建立成功后,会触发active事件
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf byteBuf = ctx.alloc().buffer(18);
byteBuf.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,16, 17});
ctx.writeAndFlush(byteBuf);
ctx.close();
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}
输出
17:59:56.641 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x5a9e5e77, L:/127.0.0.1:8080 - R:/127.0.0.1:52585] REGISTERED
17:59:56.646 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x5a9e5e77, L:/127.0.0.1:8080 - R:/127.0.0.1:52585] ACTIVE
17:59:56.672 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd5ddb572, L:/127.0.0.1:8080 - R:/127.0.0.1:52602] REGISTERED
17:59:56.672 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd5ddb572, L:/127.0.0.1:8080 - R:/127.0.0.1:52602] ACTIVE
17:59:56.702 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd5ddb572, L:/127.0.0.1:8080 - R:/127.0.0.1:52602] READ: 18B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 10 11 |.. |
+--------+-------------------------------------------------+----------------+
17:59:56.702 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x5a9e5e77, L:/127.0.0.1:8080 - R:/127.0.0.1:52585] READ: 18B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 10 11 |.. |
+--------+-------------------------------------------------+----------------+
............
17:59:56.892 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0x968932ca, L:/127.0.0.1:8080 - R:/127.0.0.1:52738] READ: 18B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
|00000010| 10 11 |.. |
+--------+-------------------------------------------------+----------------+
17:59:56.893 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0x968932ca, L:/127.0.0.1:8080 - R:/127.0.0.1:52738] READ COMPLETE
17:59:56.895 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0x968932ca, L:/127.0.0.1:8080 - R:/127.0.0.1:52738] READ COMPLETE
17:59:56.896 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0x968932ca, L:/127.0.0.1:8080 ! R:/127.0.0.1:52738] INACTIVE
17:59:56.896 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0x968932ca, L:/127.0.0.1:8080 ! R:/127.0.0.1:52738] UNREGISTERED
1.6 解决方法二-定长解码器
源码
/**
* A decoder that splits the received {@link ByteBuf}s by the fixed number
* of bytes. For example, if you received the following four fragmented packets:
* <pre>
* +---+----+------+----+
* | A | BC | DEFG | HI |
* +---+----+------+----+
* </pre>
* A {@link FixedLengthFrameDecoder}{@code (3)} will decode them into the
* following three packets with the fixed length:
* <pre>
* +-----+-----+-----+
* | ABC | DEF | GHI |
* +-----+-----+-----+
* </pre>
*/
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {}
server代码
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
/**
* @program: netty-study
* @description: 粘包半包示例代码
* @author: SunYang
* @create: 2021-08-22 14:13
**/
@Slf4j(topic = "c.Demo")
public class NianBanServerDemo {
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
// 设置channel接收缓冲区大小 影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍
// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
// 等于我们自己创建的那种ByteBuf,但是这里这个是一个可以根据接收数据大小自动调整的
// serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16,16));
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 定长解码器
ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.debug("stoped");
}
}
public static void main(String[] args) {
new NianBanServerDemo().start();
}
}
client代码
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.Random;
/**
* @program: netty-study
* @description: 粘包半包解决方法二定长解码器
* @author: SunYang
* @create: 2021-08-22 19:00
**/
@Slf4j(topic = "c.Demo")
public class FixedLengthFrameDecoderClient {
public static void main(String[] args) {
send();
System.out.println("结束");
}
private static void send() {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
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';
ByteBuf buffer = ctx.alloc().buffer();
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[10];
for (int j = 0; j < r.nextInt(10); j++) {
bytes[j] = (byte) c;
}
c++;
buffer.writeBytes(bytes);
}
ctx.writeAndFlush(buffer);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}
客户端输出
19:16:48.907 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x0bd8f283] REGISTERED
19:16:48.915 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x0bd8f283] CONNECT: /127.0.0.1:8080
19:16:48.927 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x0bd8f283, L:/127.0.0.1:50313 - R:/127.0.0.1:8080] ACTIVE
19:16:48.927 [nioEventLoopGroup-2-1] c.Demo - sending...
19:16:49.001 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x0bd8f283, L:/127.0.0.1:50313 - R:/127.0.0.1:8080] WRITE: 100B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 00 00 00 00 00 00 00 62 62 62 62 62 62 |aaa.......bbbbbb|
|00000010| 00 00 00 00 63 63 63 63 63 00 00 00 00 00 00 00 |....ccccc.......|
|00000020| 00 00 00 00 00 00 00 00 65 65 65 65 00 00 00 00 |........eeee....|
|00000030| 00 00 66 66 00 00 00 00 00 00 00 00 67 67 67 00 |..ff........ggg.|
|00000040| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|00000050| 69 00 00 00 00 00 00 00 00 00 6a 6a 6a 00 00 00 |i.........jjj...|
|00000060| 00 00 00 00 |.... |
+--------+-------------------------------------------------+----------------+
19:16:49.004 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x0bd8f283, L:/127.0.0.1:50313 - R:/127.0.0.1:8080] FLUSH
服务端输出
19:16:49.080 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] REGISTERED
19:16:49.090 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] ACTIVE
19:16:49.210 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 00 00 00 00 00 00 00 |aaa....... |
+--------+-------------------------------------------------+----------------+
19:16:49.211 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62 62 62 62 00 00 00 00 |bbbbbb.... |
+--------+-------------------------------------------------+----------------+
19:16:49.212 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 63 63 63 63 63 00 00 00 00 00 |ccccc..... |
+--------+-------------------------------------------------+----------------+
19:16:49.212 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 00 00 00 00 00 00 00 |.......... |
+--------+-------------------------------------------------+----------------+
19:16:49.212 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 65 65 65 65 00 00 00 00 00 00 |eeee...... |
+--------+-------------------------------------------------+----------------+
19:16:49.213 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 66 66 00 00 00 00 00 00 00 00 |ff........ |
+--------+-------------------------------------------------+----------------+
19:16:49.213 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 67 67 67 00 00 00 00 00 00 00 |ggg....... |
+--------+-------------------------------------------------+----------------+
19:16:49.213 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 00 00 00 00 00 00 00 |.......... |
+--------+-------------------------------------------------+----------------+
19:16:49.214 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 69 00 00 00 00 00 00 00 00 00 |i......... |
+--------+-------------------------------------------------+----------------+
19:16:49.214 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 6a 6a 6a 00 00 00 00 00 00 00 |jjj....... |
+--------+-------------------------------------------------+----------------+
19:16:49.217 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x6c1d9afa, L:/127.0.0.1:8080 - R:/127.0.0.1:50313] READ COMPLETE
缺点是,数据包的大小不好把握
- 长度定的太大,浪费
- 长度定的太小,对某些数据包又显得不够
1.7 解决方法三-行解码器(分隔符)
服务端加入,默认以 \n 或 \r\n 作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常
server代码
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
/**
* @program: netty-study
* @description: 粘包半包示例代码
* @author: SunYang
* @create: 2021-08-22 14:13
**/
@Slf4j(topic = "c.Demo")
public class NianBanServerDemo {
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
// 设置channel接收缓冲区大小 影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍
// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
// 等于我们自己创建的那种ByteBuf,但是这里这个是一个可以根据接收数据大小自动调整的
// serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16,16));
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 定长解码器
// ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
// 行解码器,分隔符解码器
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.debug("stoped");
}
}
public static void main(String[] args) {
new NianBanServerDemo().start();
}
}
client代码
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Random;
/**
* @program: netty-study
* @description: 粘包半包解决方法三-行解码器(分隔符)
* @author: SunYang
* @create: 2021-08-22 19:23
**/
@Slf4j(topic = "c.Demo")
public class LineBasedFrameDecoderClient {
public static void main(String[] args) {
send();
System.out.println("结束");
}
public static StringBuilder makeString(char c, int len) {
StringBuilder sb = new StringBuilder(len + 2);
for (int i = 0; i < len; i++) {
sb.append(c);
}
sb.append("\n");
return sb;
}
private static void send() {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
// 会在连接 channel建立成功后,会触发active事件
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("sending...");
char c = '0';
Random r = new Random();
ByteBuf byteBuf = ctx.alloc().buffer();
for (int i = 0; i < 10; i++) {
StringBuilder sb = makeString(c, r.nextInt(256) + 1);
c++;
byteBuf.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));
}
ctx.writeAndFlush(byteBuf);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}
客户端输出
19:30:02.551 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x040dfcac] REGISTERED
19:30:02.559 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x040dfcac] CONNECT: /127.0.0.1:8080
19:30:02.570 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x040dfcac, L:/127.0.0.1:51333 - R:/127.0.0.1:8080] ACTIVE
19:30:02.570 [nioEventLoopGroup-2-1] c.Demo - sending...
19:30:02.652 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x040dfcac, L:/127.0.0.1:51333 - R:/127.0.0.1:8080] WRITE: 1110B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000010| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000020| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000030| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000040| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000050| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000060| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000070| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000080| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000090| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000a0| 30 30 30 30 30 30 30 30 30 30 30 0a 31 31 31 31 |00000000000.1111|
|000000b0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|000000c0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|000000d0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|000000e0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|000000f0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000100| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000110| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000120| 31 31 31 31 31 31 31 31 31 31 31 0a 32 32 32 32 |11111111111.2222|
|00000130| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000140| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 0a |222222222222222.|
|00000150| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000160| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000170| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000180| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000190| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000001a0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000001b0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000001c0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000001d0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000001e0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000001f0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000200| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000210| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000220| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 0a 34 |33333333333333.4|
|00000230| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000240| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000250| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000260| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000270| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000280| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000290| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|000002a0| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|000002b0| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|000002c0| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|000002d0| 34 34 0a 35 35 35 0a 36 36 36 36 36 36 36 36 36 |44.555.666666666|
|000002e0| 36 36 0a 37 37 37 37 37 37 37 37 37 37 37 37 37 |66.7777777777777|
|000002f0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000300| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000310| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000320| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000330| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000340| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000350| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000360| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000370| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000380| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000390| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000003a0| 37 37 37 37 37 37 37 37 37 37 37 37 37 0a 38 38 |7777777777777.88|
|000003b0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|000003c0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|000003d0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|000003e0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|000003f0| 38 38 38 38 38 38 38 38 38 0a 39 39 39 39 39 39 |888888888.999999|
|00000400| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000410| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000420| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000430| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000440| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000450| 39 39 39 39 39 0a |99999. |
+--------+-------------------------------------------------+----------------+
19:30:02.654 [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler - [id: 0x040dfcac, L:/127.0.0.1:51333 - R:/127.0.0.1:8080] FLUSH
服务端输出
19:30:02.647 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] REGISTERED
19:30:02.657 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] ACTIVE
19:30:02.770 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 171B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000010| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000020| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000030| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000040| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000050| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000060| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000070| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000080| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|00000090| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
|000000a0| 30 30 30 30 30 30 30 30 30 30 30 |00000000000 |
+--------+-------------------------------------------------+----------------+
19:30:02.772 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 127B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000010| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000020| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000030| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000040| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000050| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000060| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
|00000070| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |111111111111111 |
+--------+-------------------------------------------------+----------------+
19:30:02.773 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 35B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000010| 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222|
|00000020| 32 32 32 |222 |
+--------+-------------------------------------------------+----------------+
19:30:02.774 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 222B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000010| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000020| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000030| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000040| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000050| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000060| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000070| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000080| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|00000090| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000a0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000b0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000c0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
|000000d0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |33333333333333 |
+--------+-------------------------------------------------+----------------+
19:30:02.775 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 163B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000010| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000020| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000030| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000040| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000050| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000060| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000070| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000080| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|00000090| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
|000000a0| 34 34 34 |444 |
+--------+-------------------------------------------------+----------------+
19:30:02.775 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 3B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 35 35 35 |555 |
+--------+-------------------------------------------------+----------------+
19:30:02.776 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 11B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 36 36 36 36 36 36 36 36 36 36 36 |66666666666 |
+--------+-------------------------------------------------+----------------+
19:30:02.776 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 202B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000010| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000020| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000030| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000040| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000050| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000060| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000070| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000080| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|00000090| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000000a0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000000b0| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
|000000c0| 37 37 37 37 37 37 37 37 37 37 |7777777777 |
+--------+-------------------------------------------------+----------------+
19:30:02.777 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 75B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000010| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000020| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000030| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
|00000040| 38 38 38 38 38 38 38 38 38 38 38 |88888888888 |
+--------+-------------------------------------------------+----------------+
19:30:02.777 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ: 91B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000010| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000020| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000030| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000040| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
|00000050| 39 39 39 39 39 39 39 39 39 39 39 |99999999999 |
+--------+-------------------------------------------------+----------------+
19:30:02.780 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x88c9476d, L:/127.0.0.1:8080 - R:/127.0.0.1:51333] READ COMPLETE
缺点,
-
处理字符数据比较合适,但如果内容本身包含了分隔符(字节数据常常会有此情况),那么就会解析错误
-
而且要遍历每个字节去寻找分隔符。影响性能。
1.8 解决方法四-LTC解码器
其实就类似于协议,只不过协议是一种规定,这个就等于是你自己定义一个灵活的协议,适用于你的服务器和客户端的通讯。
直接看源码顶部的示例,解释的很清楚,摘抄一个最全的
lengthFieldOffset = 1 (= the length of HDR1) // 长度字段偏移量
lengthFieldLength = 2 // 长度字段长度
lengthAdjustment = 1 (= the length of HDR2) // 以长度字段为基准,还有几个字节是内容
initialBytesToStrip = 3 (= the length of HDR1 + LEN) // 从头剥离几个字节
// 长度字段偏移量为1,长度字段长度为2,以长度字段为基准,1个字节后是内容,从头剥离3个字节
// 一个字节后的2个字节表示的是内容长度为12个字节,1个字节后是正式内容,剥离前面的三个字节,只要后面的。
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
示例
package com.sunyang.netty.study.nettydemo;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
/**
* @program: netty-study
* @description: 粘包半包解决方法四-LTC解码器客户端
* @author: SunYang
* @create: 2021-08-22 21:21
**/
@Slf4j(topic = "c.Demo")
public class LengthFieldDecoderClient {
public static void main(String[] args) {
EmbeddedChannel channel = new EmbeddedChannel(
new LengthFieldBasedFrameDecoder(1024, 0, 4, 1, 4),
new LoggingHandler(LogLevel.DEBUG)
);
// 4个字节的内容长度,实际内容
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
send(byteBuf, "Hello, world");
send(byteBuf, "Hi!");
channel.writeInbound(byteBuf);
}
private static void send(ByteBuf byteBuf, String content) {
byte[] bytes = content.getBytes(StandardCharsets.UTF_8); // 实际内容
int length = bytes.length;
byteBuf.writeInt(length); // 字段长度
byteBuf.writeByte(97); // 用一个字节表示版本号 a
byteBuf.writeBytes(bytes);
}
}
输出
21:41:09.496 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] REGISTERED
21:41:09.506 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] ACTIVE
21:41:09.626 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 13B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 |aHello, world |
+--------+-------------------------------------------------+----------------+
21:41:09.627 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 4B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 48 69 21 |aHi! |
+--------+-------------------------------------------------+----------------+
21:41:09.627 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
Process finished with exit code 0
2. 协议设计与解析
2.1 为什么需要协议?
TCP/IP 中消息传输基于流的方式,没有边界。
协议的目的就是划定消息的边界,制定通信双方要共同遵守的通信规则
例如:在网络上传输
下雨天留客天留我不留
是中文一句著名的无标点符号句子,在没有标点符号情况下,这句话有数种拆解方式,而意思却是完全不同,所以常被用作讲述标点符号的重要性
一种解读
下雨天留客,天留,我不留
另一种解读
下雨天,留客天,留我不?留
如何设计协议呢?其实就是给网络传输的信息加上“标点符号”。但通过分隔符来断句不是很好,因为分隔符本身如果用于传输,那么必须加以区分。因此,下面一种协议较为常用
定长字节表示内容长度 + 实际内容
例如,假设一个中文字符长度为 3,按照上述协议的规则,发送信息方式如下,就不会被接收方弄错意思了
0f下雨天留客06天留09我不留
小故事
很久很久以前,一位私塾先生到一家任教。双方签订了一纸协议:“无鸡鸭亦可无鱼肉亦可白菜豆腐不可少不得束修金”。此后,私塾先生虽然认真教课,但主人家则总是给私塾先生以白菜豆腐为菜,丝毫未见鸡鸭鱼肉的款待。私塾先生先是很不解,可是后来也就想通了:主人把鸡鸭鱼肉的钱都会换为束修金的,也罢。至此双方相安无事。
年关将至,一个学年段亦告结束。私塾先生临行时,也不见主人家为他交付束修金,遂与主家理论。然主家亦振振有词:“有协议为证——无鸡鸭亦可,无鱼肉亦可,白菜豆腐不可少,不得束修金。这白纸黑字明摆着的,你有什么要说的呢?”
私塾先生据理力争:“协议是这样的——无鸡,鸭亦可;无鱼,肉亦可;白菜豆腐不可,少不得束修金。”
双方唇枪舌战,你来我往,真个是不亦乐乎!
这里的束修金,也作“束脩”,应当是泛指教师应当得到的报酬
2.2 redis协议
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Random;
/**
* @program: netty-study
* @description: redis
* @author: SunYang
* @create: 2021-08-22 22:03
**/
@Slf4j(topic = "c.Demo")
public class RedisProtocol {
/**
* set name zhangsan 中间要加 /r /n
* 发送整个数组的长度(也就是元素的个数) *3 /r /n
* 第一个元素的长度 $3 /r /n
* 值 set /r /n
* 第一个元素的长度 $4 /r /n
* 值 name /r /n
* 第一个元素的长度 $8 /r /n
* 值 zhangsan /r /n
* **/
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
byte[] LINE = {13, 10};
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
// 会在连接 channel 建立成功后,会触发 active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) {
// set(ctx);
// get(ctx);
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes("*3".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("$3".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("set".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("$4".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("name".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("$5".getBytes());
buf.writeBytes(LINE);
buf.writeBytes("zhangsan".getBytes());
buf.writeBytes(LINE);
ctx.writeAndFlush(buf);
}
// private void get(ChannelHandlerContext ctx) {
// ByteBuf buf = ctx.alloc().buffer();
// buf.writeBytes("*2".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("$3".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("get".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("$4".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("name".getBytes());
// buf.writeBytes(LINE);
// ctx.writeAndFlush(buf);
// }
// private void set(ChannelHandlerContext ctx) {
// ByteBuf buf = ctx.alloc().buffer();
// buf.writeBytes("*3".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("$3".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("set".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("$4".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("name".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("$5".getBytes());
// buf.writeBytes(LINE);
// buf.writeBytes("zhangsan".getBytes());
// buf.writeBytes(LINE);
// ctx.writeAndFlush(buf);
// }
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println(buf.toString(StandardCharsets.UTF_8));
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("47.94.209.96", 6379)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}
2.3 Http协议
server端代码
package com.sunyang.netty.study.nettydemo;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
/**
* @program: netty-study
* @description: Http协议
* @author: SunYang
* @create: 2021-08-22 22:36
**/
@Slf4j(topic = "c.Demo")
public class HttpProtocolServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
// 获取请求
log.debug(msg.uri());
// 返回响应
DefaultFullHttpResponse response = new DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);
// 在响应头中加一个响应体的长度,不然浏览器会一直等待接收更多内容,因为他不知道响应内容多长。
byte[] bytes = "<h1>Hello SunYang</h1>".getBytes(StandardCharsets.UTF_8);
response.headers().setInt(CONTENT_LENGTH, bytes.length);
response.content().writeBytes(bytes);
// 写回响应
ctx.writeAndFlush(response);
}
});
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080);
channelFuture.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.debug("stoped");
}
}
}
输出
22:53:56.545 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x8738b829, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:50006] REGISTERED
22:53:56.545 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] REGISTERED
22:53:56.552 [nioEventLoopGroup-3-1] io.netty.handler.logging.LoggingHandler - [id: 0x8738b829, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:50006] ACTIVE
22:53:56.552 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] ACTIVE
22:53:56.592 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] READ: 663B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a |GET / HTTP/1.1..|
|00000010| 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a |Host: localhost:|
|00000020| 38 30 38 30 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e |8080..Connection|
|00000030| 3a 20 6b 65 65 70 2d 61 6c 69 76 65 0d 0a 73 65 |: keep-alive..se|
|00000040| 63 2d 63 68 2d 75 61 3a 20 22 43 68 72 6f 6d 69 |c-ch-ua: "Chromi|
|00000050| 75 6d 22 3b 76 3d 22 39 32 22 2c 20 22 20 4e 6f |um";v="92", " No|
|00000060| 74 20 41 3b 42 72 61 6e 64 22 3b 76 3d 22 39 39 |t A;Brand";v="99|
|00000070| 22 2c 20 22 47 6f 6f 67 6c 65 20 43 68 72 6f 6d |", "Google Chrom|
|00000080| 65 22 3b 76 3d 22 39 32 22 0d 0a 73 65 63 2d 63 |e";v="92"..sec-c|
|00000090| 68 2d 75 61 2d 6d 6f 62 69 6c 65 3a 20 3f 30 0d |h-ua-mobile: ?0.|
|000000a0| 0a 55 70 67 72 61 64 65 2d 49 6e 73 65 63 75 72 |.Upgrade-Insecur|
|000000b0| 65 2d 52 65 71 75 65 73 74 73 3a 20 31 0d 0a 55 |e-Requests: 1..U|
|000000c0| 73 65 72 2d 41 67 65 6e 74 3a 20 4d 6f 7a 69 6c |ser-Agent: Mozil|
|000000d0| 6c 61 2f 35 2e 30 20 28 57 69 6e 64 6f 77 73 20 |la/5.0 (Windows |
|000000e0| 4e 54 20 31 30 2e 30 3b 20 57 69 6e 36 34 3b 20 |NT 10.0; Win64; |
|000000f0| 78 36 34 29 20 41 70 70 6c 65 57 65 62 4b 69 74 |x64) AppleWebKit|
|00000100| 2f 35 33 37 2e 33 36 20 28 4b 48 54 4d 4c 2c 20 |/537.36 (KHTML, |
|00000110| 6c 69 6b 65 20 47 65 63 6b 6f 29 20 43 68 72 6f |like Gecko) Chro|
|00000120| 6d 65 2f 39 32 2e 30 2e 34 35 31 35 2e 31 35 39 |me/92.0.4515.159|
|00000130| 20 53 61 66 61 72 69 2f 35 33 37 2e 33 36 0d 0a | Safari/537.36..|
|00000140| 41 63 63 65 70 74 3a 20 74 65 78 74 2f 68 74 6d |Accept: text/htm|
|00000150| 6c 2c 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 68 |l,application/xh|
|00000160| 74 6d 6c 2b 78 6d 6c 2c 61 70 70 6c 69 63 61 74 |tml+xml,applicat|
|00000170| 69 6f 6e 2f 78 6d 6c 3b 71 3d 30 2e 39 2c 69 6d |ion/xml;q=0.9,im|
|00000180| 61 67 65 2f 61 76 69 66 2c 69 6d 61 67 65 2f 77 |age/avif,image/w|
|00000190| 65 62 70 2c 69 6d 61 67 65 2f 61 70 6e 67 2c 2a |ebp,image/apng,*|
|000001a0| 2f 2a 3b 71 3d 30 2e 38 2c 61 70 70 6c 69 63 61 |/*;q=0.8,applica|
|000001b0| 74 69 6f 6e 2f 73 69 67 6e 65 64 2d 65 78 63 68 |tion/signed-exch|
|000001c0| 61 6e 67 65 3b 76 3d 62 33 3b 71 3d 30 2e 39 0d |ange;v=b3;q=0.9.|
|000001d0| 0a 53 65 63 2d 46 65 74 63 68 2d 53 69 74 65 3a |.Sec-Fetch-Site:|
|000001e0| 20 6e 6f 6e 65 0d 0a 53 65 63 2d 46 65 74 63 68 | none..Sec-Fetch|
|000001f0| 2d 4d 6f 64 65 3a 20 6e 61 76 69 67 61 74 65 0d |-Mode: navigate.|
|00000200| 0a 53 65 63 2d 46 65 74 63 68 2d 55 73 65 72 3a |.Sec-Fetch-User:|
|00000210| 20 3f 31 0d 0a 53 65 63 2d 46 65 74 63 68 2d 44 | ?1..Sec-Fetch-D|
|00000220| 65 73 74 3a 20 64 6f 63 75 6d 65 6e 74 0d 0a 41 |est: document..A|
|00000230| 63 63 65 70 74 2d 45 6e 63 6f 64 69 6e 67 3a 20 |ccept-Encoding: |
|00000240| 67 7a 69 70 2c 20 64 65 66 6c 61 74 65 2c 20 62 |gzip, deflate, b|
|00000250| 72 0d 0a 41 63 63 65 70 74 2d 4c 61 6e 67 75 61 |r..Accept-Langua|
|00000260| 67 65 3a 20 7a 68 2c 7a 68 2d 43 4e 3b 71 3d 30 |ge: zh,zh-CN;q=0|
|00000270| 2e 39 2c 7a 68 2d 54 57 3b 71 3d 30 2e 38 2c 65 |.9,zh-TW;q=0.8,e|
|00000280| 6e 2d 55 53 3b 71 3d 30 2e 37 2c 65 6e 3b 71 3d |n-US;q=0.7,en;q=|
|00000290| 30 2e 36 0d 0a 0d 0a |0.6.... |
+--------+-------------------------------------------------+----------------+
22:53:56.665 [nioEventLoopGroup-3-2] c.Demo - /
22:53:56.683 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] WRITE: 61B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.|
|00000010| 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a |.content-length:|
|00000020| 20 32 32 0d 0a 0d 0a 3c 68 31 3e 48 65 6c 6c 6f | 22....<h1>Hello|
|00000030| 20 53 75 6e 59 61 6e 67 3c 2f 68 31 3e | SunYang</h1> |
+--------+-------------------------------------------------+----------------+
22:53:56.685 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] FLUSH
22:53:56.688 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] READ COMPLETE
22:53:57.099 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] READ: 589B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 47 45 54 20 2f 66 61 76 69 63 6f 6e 2e 69 63 6f |GET /favicon.ico|
|00000010| 20 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a | HTTP/1.1..Host:|
|00000020| 20 6c 6f 63 61 6c 68 6f 73 74 3a 38 30 38 30 0d | localhost:8080.|
|00000030| 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 6b 65 65 |.Connection: kee|
|00000040| 70 2d 61 6c 69 76 65 0d 0a 73 65 63 2d 63 68 2d |p-alive..sec-ch-|
|00000050| 75 61 3a 20 22 43 68 72 6f 6d 69 75 6d 22 3b 76 |ua: "Chromium";v|
|00000060| 3d 22 39 32 22 2c 20 22 20 4e 6f 74 20 41 3b 42 |="92", " Not A;B|
|00000070| 72 61 6e 64 22 3b 76 3d 22 39 39 22 2c 20 22 47 |rand";v="99", "G|
|00000080| 6f 6f 67 6c 65 20 43 68 72 6f 6d 65 22 3b 76 3d |oogle Chrome";v=|
|00000090| 22 39 32 22 0d 0a 73 65 63 2d 63 68 2d 75 61 2d |"92"..sec-ch-ua-|
|000000a0| 6d 6f 62 69 6c 65 3a 20 3f 30 0d 0a 55 73 65 72 |mobile: ?0..User|
|000000b0| 2d 41 67 65 6e 74 3a 20 4d 6f 7a 69 6c 6c 61 2f |-Agent: Mozilla/|
|000000c0| 35 2e 30 20 28 57 69 6e 64 6f 77 73 20 4e 54 20 |5.0 (Windows NT |
|000000d0| 31 30 2e 30 3b 20 57 69 6e 36 34 3b 20 78 36 34 |10.0; Win64; x64|
|000000e0| 29 20 41 70 70 6c 65 57 65 62 4b 69 74 2f 35 33 |) AppleWebKit/53|
|000000f0| 37 2e 33 36 20 28 4b 48 54 4d 4c 2c 20 6c 69 6b |7.36 (KHTML, lik|
|00000100| 65 20 47 65 63 6b 6f 29 20 43 68 72 6f 6d 65 2f |e Gecko) Chrome/|
|00000110| 39 32 2e 30 2e 34 35 31 35 2e 31 35 39 20 53 61 |92.0.4515.159 Sa|
|00000120| 66 61 72 69 2f 35 33 37 2e 33 36 0d 0a 41 63 63 |fari/537.36..Acc|
|00000130| 65 70 74 3a 20 69 6d 61 67 65 2f 61 76 69 66 2c |ept: image/avif,|
|00000140| 69 6d 61 67 65 2f 77 65 62 70 2c 69 6d 61 67 65 |image/webp,image|
|00000150| 2f 61 70 6e 67 2c 69 6d 61 67 65 2f 73 76 67 2b |/apng,image/svg+|
|00000160| 78 6d 6c 2c 69 6d 61 67 65 2f 2a 2c 2a 2f 2a 3b |xml,image/*,*/*;|
|00000170| 71 3d 30 2e 38 0d 0a 53 65 63 2d 46 65 74 63 68 |q=0.8..Sec-Fetch|
|00000180| 2d 53 69 74 65 3a 20 73 61 6d 65 2d 6f 72 69 67 |-Site: same-orig|
|00000190| 69 6e 0d 0a 53 65 63 2d 46 65 74 63 68 2d 4d 6f |in..Sec-Fetch-Mo|
|000001a0| 64 65 3a 20 6e 6f 2d 63 6f 72 73 0d 0a 53 65 63 |de: no-cors..Sec|
|000001b0| 2d 46 65 74 63 68 2d 44 65 73 74 3a 20 69 6d 61 |-Fetch-Dest: ima|
|000001c0| 67 65 0d 0a 52 65 66 65 72 65 72 3a 20 68 74 74 |ge..Referer: htt|
|000001d0| 70 3a 2f 2f 6c 6f 63 61 6c 68 6f 73 74 3a 38 30 |p://localhost:80|
|000001e0| 38 30 2f 0d 0a 41 63 63 65 70 74 2d 45 6e 63 6f |80/..Accept-Enco|
|000001f0| 64 69 6e 67 3a 20 67 7a 69 70 2c 20 64 65 66 6c |ding: gzip, defl|
|00000200| 61 74 65 2c 20 62 72 0d 0a 41 63 63 65 70 74 2d |ate, br..Accept-|
|00000210| 4c 61 6e 67 75 61 67 65 3a 20 7a 68 2c 7a 68 2d |Language: zh,zh-|
|00000220| 43 4e 3b 71 3d 30 2e 39 2c 7a 68 2d 54 57 3b 71 |CN;q=0.9,zh-TW;q|
|00000230| 3d 30 2e 38 2c 65 6e 2d 55 53 3b 71 3d 30 2e 37 |=0.8,en-US;q=0.7|
|00000240| 2c 65 6e 3b 71 3d 30 2e 36 0d 0a 0d 0a |,en;q=0.6.... |
+--------+-------------------------------------------------+----------------+
22:53:57.100 [nioEventLoopGroup-3-2] c.Demo - /favicon.ico
22:53:57.101 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] WRITE: 61B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.|
|00000010| 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a |.content-length:|
|00000020| 20 32 32 0d 0a 0d 0a 3c 68 31 3e 48 65 6c 6c 6f | 22....<h1>Hello|
|00000030| 20 53 75 6e 59 61 6e 67 3c 2f 68 31 3e | SunYang</h1> |
+--------+-------------------------------------------------+----------------+
22:53:57.101 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] FLUSH
22:53:57.102 [nioEventLoopGroup-3-2] io.netty.handler.logging.LoggingHandler - [id: 0xd6b41c43, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:56747] READ COMPLETE
2.4 自定义协议要素
-
魔数:用来第一时间判定是否是无效数据包。例如java的class文件的开头都是CAFEBABE,用来判断这个文件是否是java文件
-
版本号,可以支持协议的升级(如果协议升级,发生了变化,需要依靠版本号获知)
-
序列化算法: 消息正文到底采用哪种序列化反序列化方式,可以由此扩展,例如:json,protobuf,hessian,jdk
-
指令类型:是登录,注册,单聊,群聊,跟业务相关
-
请求序号:为了双工通信,提供异步能力,例如:因为右滑动窗口的存在,消息不是发一条,确认后回一条,是一次发很多,而且发送的顺序也不保证,所以要有序号来确定顺序
-
正文长度
-
消息正文
Codec编解码代码
package com.sunyang.netty.study.protocol;
import com.sunyang.netty.study.message.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
/**
* @Author: sunyang
* @Date: 2021/8/23
* @Description:
*/
@Slf4j(topic = "c.Demo")
public class MessageCodec extends ByteToMessageCodec<Message> {
@Override
public void encode(ChannelHandlerContext channelHandlerContext, Message msg, ByteBuf out) throws Exception {
// 1. 4个字节魔数 yang
out.writeBytes(new byte[]{'y', 'a', 'n', 'g'});
// 2. 1个字节版本号
out.writeByte(1);
// 3. 1个字节序列化算法方式 jdk 0, json 1
out.writeByte(0);
// 4. 1个字节指令类型
out.writeByte(msg.getMessageType());
// 5. 4个字节请求序号
out.writeInt(msg.getSequenceId());
// 因为加一起一共是15个字节,因为要尽量要数据大小为2的n次方倍,所以加一个字节的填充位
out.writeByte(0xff);
// 6. 获取内容的字节数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(msg);
// 7. 获取内容的长度
byte[] bytes = bos.toByteArray();
out.writeInt(bytes.length);
// 8. 写入内容
out.writeBytes(bytes);
}
@Override
public void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
int magicNum = in.readInt();
byte version = in.readByte();
byte serializerType = in.readByte();
byte messageType = in.readByte();
int sequenceId = in.readInt();
in.readByte();
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
Message message = (Message) ois.readObject();
log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
log.debug("{}", message);
out.add(message);
}
}
测试端代码
package com.sunyang.netty.study.nettydemo;
import com.sunyang.netty.study.message.LoginRequestMessage;
import com.sunyang.netty.study.protocol.MessageCodec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: sunyang
* @Date: 2021/8/23
* @Description:
*/
@Slf4j(topic = "c.Demo")
public class MessageCodecDemo {
public static void main(String[] args) throws Exception {
EmbeddedChannel channel = new EmbeddedChannel(
// 加入帧解码器
new LengthFieldBasedFrameDecoder(1024, 12, 4, 0, 0),
new LoggingHandler(LogLevel.DEBUG),
new MessageCodec());
// encode
LoginRequestMessage message = new LoginRequestMessage("zhangsan", "123", "张三");
channel.writeOutbound(message);
// decode
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
new MessageCodec().encode(null, message, buf);
// 入站
channel.writeInbound(buf);
}
}
输出
13:57:18.296 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] REGISTERED
13:57:18.298 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] ACTIVE
13:57:18.370 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] WRITE: 267B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 79 61 6e 67 01 00 00 00 00 00 00 ff 00 00 00 fb |yang............|
|00000010| ac ed 00 05 73 72 00 33 63 6f 6d 2e 73 75 6e 79 |....sr.3com.suny|
|00000020| 61 6e 67 2e 6e 65 74 74 79 2e 73 74 75 64 79 2e |ang.netty.study.|
|00000030| 6d 65 73 73 61 67 65 2e 4c 6f 67 69 6e 52 65 71 |message.LoginReq|
|00000040| 75 65 73 74 4d 65 73 73 61 67 65 3c da 1f b4 45 |uestMessage<...E|
|00000050| bd 6e ea 02 00 03 4c 00 08 6e 69 63 6b 6e 61 6d |.n....L..nicknam|
|00000060| 65 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 |et..Ljava/lang/S|
|00000070| 74 72 69 6e 67 3b 4c 00 08 70 61 73 73 77 6f 72 |tring;L..passwor|
|00000080| 64 71 00 7e 00 01 4c 00 08 75 73 65 72 6e 61 6d |dq.~..L..usernam|
|00000090| 65 71 00 7e 00 01 78 72 00 27 63 6f 6d 2e 73 75 |eq.~..xr.'com.su|
|000000a0| 6e 79 61 6e 67 2e 6e 65 74 74 79 2e 73 74 75 64 |nyang.netty.stud|
|000000b0| 79 2e 6d 65 73 73 61 67 65 2e 4d 65 73 73 61 67 |y.message.Messag|
|000000c0| 65 55 55 6e dd 17 45 d2 31 02 00 02 49 00 0b 6d |eUUn..E.1...I..m|
|000000d0| 65 73 73 61 67 65 54 79 70 65 49 00 0a 73 65 71 |essageTypeI..seq|
|000000e0| 75 65 6e 63 65 49 64 78 70 00 00 00 00 00 00 00 |uenceIdxp.......|
|000000f0| 00 74 00 06 e5 bc a0 e4 b8 89 74 00 03 31 32 33 |.t........t..123|
|00000100| 74 00 08 7a 68 61 6e 67 73 61 6e |t..zhangsan |
+--------+-------------------------------------------------+----------------+
13:57:18.371 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] FLUSH
13:57:18.383 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 267B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 79 61 6e 67 01 00 00 00 00 00 00 ff 00 00 00 fb |yang............|
|00000010| ac ed 00 05 73 72 00 33 63 6f 6d 2e 73 75 6e 79 |....sr.3com.suny|
|00000020| 61 6e 67 2e 6e 65 74 74 79 2e 73 74 75 64 79 2e |ang.netty.study.|
|00000030| 6d 65 73 73 61 67 65 2e 4c 6f 67 69 6e 52 65 71 |message.LoginReq|
|00000040| 75 65 73 74 4d 65 73 73 61 67 65 3c da 1f b4 45 |uestMessage<...E|
|00000050| bd 6e ea 02 00 03 4c 00 08 6e 69 63 6b 6e 61 6d |.n....L..nicknam|
|00000060| 65 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 |et..Ljava/lang/S|
|00000070| 74 72 69 6e 67 3b 4c 00 08 70 61 73 73 77 6f 72 |tring;L..passwor|
|00000080| 64 71 00 7e 00 01 4c 00 08 75 73 65 72 6e 61 6d |dq.~..L..usernam|
|00000090| 65 71 00 7e 00 01 78 72 00 27 63 6f 6d 2e 73 75 |eq.~..xr.'com.su|
|000000a0| 6e 79 61 6e 67 2e 6e 65 74 74 79 2e 73 74 75 64 |nyang.netty.stud|
|000000b0| 79 2e 6d 65 73 73 61 67 65 2e 4d 65 73 73 61 67 |y.message.Messag|
|000000c0| 65 55 55 6e dd 17 45 d2 31 02 00 02 49 00 0b 6d |eUUn..E.1...I..m|
|000000d0| 65 73 73 61 67 65 54 79 70 65 49 00 0a 73 65 71 |essageTypeI..seq|
|000000e0| 75 65 6e 63 65 49 64 78 70 00 00 00 00 00 00 00 |uenceIdxp.......|
|000000f0| 00 74 00 06 e5 bc a0 e4 b8 89 74 00 03 31 32 33 |.t........t..123|
|00000100| 74 00 08 7a 68 61 6e 67 73 61 6e |t..zhangsan |
+--------+-------------------------------------------------+----------------+
13:57:18.429 [main] c.Demo - 2036428391, 1, 0, 0, 0, 251
13:57:18.431 [main] c.Demo - LoginRequestMessage(super=Message(sequenceId=0, messageType=0), username=zhangsan, password=123, nickname=张三)
13:57:18.431 [main] io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
Process finished with exit code 0
2.5 @Sharable
Netty提供的handler类
不记录状态信息的handler就是线程安全的,记录状态信息的就是非安全的,Netty中加了@Sharable的handler可以被共享。
例如:
-
LengthFieldBasedFrameDecoder() // 他需要记录多次读事件的中间状态。
什么时候可以加@Sharable
- 当handler不保存读取状态时,就可以安全的在多线程下被共享
- 但要注意对于编解码器类,不能继承 ByteToMessageCodec 或 CombinedChannelDuplexHandler 父类,他们的构造方法对 @Sharable 有限制
- 如果能确保编解码器不会保存状态,可以继承 MessageToMessageCodec 父类
自定义的handler类
package com.sunyang.netty.study.protocol;
import com.sunyang.netty.study.message.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
/**
* @Author: sunyang
* @Date: 2021/8/23
* @Description: 可共享的编解码器
*/
@Slf4j(topic = "c.Demo")
@ChannelHandler.Sharable
/**
* 必须和LengthFieldBasedFrameDecoder 一起使用,确保接到的 ByteBuf消息是完整的,
* **/
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {
ByteBuf out = ctx.alloc().buffer();
// 1. 4个字节魔数 yang
out.writeBytes(new byte[]{'y', 'a', 'n', 'g'});
// 2. 1个字节版本号
out.writeByte(1);
// 3. 1个字节序列化算法方式 jdk 0, json 1
out.writeByte(0);
// 4. 1个字节指令类型
out.writeByte(msg.getMessageType());
// 5. 4个字节请求序号
out.writeInt(msg.getSequenceId());
// 因为加一起一共是15个字节,因为要尽量要数据大小为2的n次方倍,所以加一个字节的填充位
out.writeByte(0xff);
// 6. 获取内容的字节数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(msg);
// 7. 获取内容的长度
byte[] bytes = bos.toByteArray();
out.writeInt(bytes.length);
// 8. 写入内容
out.writeBytes(bytes);
outList.add(out);
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
int magicNum = in.readInt();
byte version = in.readByte();
byte serializerType = in.readByte();
byte messageType = in.readByte();
int sequenceId = in.readInt();
in.readByte();
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
Message message = (Message) ois.readObject();
log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
log.debug("{}", message);
out.add(message);
}
}
o")
@ChannelHandler.Sharable
/**
-
必须和LengthFieldBasedFrameDecoder 一起使用,确保接到的 ByteBuf消息是完整的,
-
**/
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {@Override
protected void encode(ChannelHandlerContext ctx, Message msg, List outList) throws Exception {
ByteBuf out = ctx.alloc().buffer();
// 1. 4个字节魔数 yang
out.writeBytes(new byte[]{‘y’, ‘a’, ‘n’, ‘g’});
// 2. 1个字节版本号
out.writeByte(1);
// 3. 1个字节序列化算法方式 jdk 0, json 1
out.writeByte(0);
// 4. 1个字节指令类型
out.writeByte(msg.getMessageType());
// 5. 4个字节请求序号
out.writeInt(msg.getSequenceId());// 因为加一起一共是15个字节,因为要尽量要数据大小为2的n次方倍,所以加一个字节的填充位 out.writeByte(0xff); // 6. 获取内容的字节数组 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(msg); // 7. 获取内容的长度 byte[] bytes = bos.toByteArray(); out.writeInt(bytes.length); // 8. 写入内容 out.writeBytes(bytes); outList.add(out);
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List out) throws Exception {
int magicNum = in.readInt();
byte version = in.readByte();
byte serializerType = in.readByte();
byte messageType = in.readByte();
int sequenceId = in.readInt();
in.readByte();
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
Message message = (Message) ois.readObject();
log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
log.debug("{}", message);
out.add(message);
}
}