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));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080);
channelFuture.sync();
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 {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
// 会在连接 channel 建立成功后,会触发 active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buffer = ctx.alloc().buffer(16);
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);
}
}
});
}
});
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 个字节,而非分 10 次接收
服务端修改一下接收缓冲区,其它代码不变
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10)
影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍
本质是因为 TCP 是流式协议,消息无边界
只要是使用TCP/IP协议进行编程,就会出现黏包和半包想象
UDP协议没有
TCP 以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差
为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值
窗口实际就起到一个缓冲区的作用
,同时也能起到 流量控制的作用
TCP层面分析(滑动窗口)
如果网络比较繁忙,接收方的窗口使用完了,不能继续向下滑动,其接收到的数据势必会容易发生半包现象
(到一半窗口使用完了,如果这时候去读缓冲区,读到的就是半包的数据)
如果滑动窗口比较空闲,里面还有许多的空间没有使用,这时候客户端发送的许多条数据过来,滑动窗口将这多条数据都接收到了,那就发生了黏包现象
链路层对一次能够发送的最大数据有限制,这个限制称之为 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
短链接,发一个包建立一次连接,这样连接建立到连接断开之间就是消息的边界
,缺点效率太低
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();
}
System.out.println("finish");
}
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 {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
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.channel().close();
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}
客户端代码只是需要将之前设置滑动窗口的代码注释掉即可
如果不设置滑动窗口的话,现在的操作系统可以自行调整滑动窗口的大小
ByteBuf默认是1024,足够大了
半包用这种办法还是不好解决,因为接收方的缓冲区大小是有限的
让所有数据包长度固定(假设长度为 8 字节),服务器端加入
ch.pipeline().addLast(new FixedLengthFrameDecoder(8));
缺点是,数据包的大小不好把握
@Slf4j
public class Server2 {
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
// 调整系统的接收缓冲区(滑动窗口)
// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
// 调整 netty 的接收缓冲区(byteBuf)
serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) {
new Server2().start();
}
}
public class Client2 {
static final Logger log = LoggerFactory.getLogger(Client1.class);
public static void main(String[] args) {
send();
System.out.println("finish");
}
/*
产生定长的消息
产生一个内容为c的消息,消息的长度是len,不足len的长度使用下划线补全
*/
public static byte[] fill10Bytes(char c, int len) {
byte[] bytes = new byte[10];
Arrays.fill(bytes, (byte) '_');
for (int i = 0; i < len; i++) {
bytes[i] = (byte) c;
}
System.out.println(new String(bytes));
return bytes;
}
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) {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
// 会在连接 channel 建立成功后,会触发 active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf buf = ctx.alloc().buffer();
char c = '0';
Random r = new Random();
for (int i = 0; i < 10; i++) {
//内容长度随机:1到10之间
byte[] bytes = fill10Bytes(c, r.nextInt(10) + 1);
c++;
//将bytes数组里面的内容写入到buf中
buf.writeBytes(bytes);
}
//向服务器端发送消息
ctx.writeAndFlush(buf);
}
});
}
});
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();
}
}
}
可以发现,在这里保证了每一条消息的长度都是10
客户端将10*10=100个字节的消息一起发出去了
23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c] REGISTERED
23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c] CONNECT: /127.0.0.1:8080
23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c, L:/127.0.0.1:10305 - R:/127.0.0.1:8080] ACTIVE
000_______
11111_____
222222____
33333_____
44444444__
555_______
6666666___
77777_____
8888______
99________
23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c, L:/127.0.0.1:10305 - 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| 30 30 30 5f 5f 5f 5f 5f 5f 5f 31 31 31 31 31 5f |000_______11111_|
|00000010| 5f 5f 5f 5f 32 32 32 32 32 32 5f 5f 5f 5f 33 33 |____222222____33|
|00000020| 33 33 33 5f 5f 5f 5f 5f 34 34 34 34 34 34 34 34 |333_____44444444|
|00000030| 5f 5f 35 35 35 5f 5f 5f 5f 5f 5f 5f 36 36 36 36 |__555_______6666|
|00000040| 36 36 36 5f 5f 5f 37 37 37 37 37 5f 5f 5f 5f 5f |666___77777_____|
|00000050| 38 38 38 38 5f 5f 5f 5f 5f 5f 39 39 5f 5f 5f 5f |8888______99____|
|00000060| 5f 5f 5f 5f |____ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c, L:/127.0.0.1:10305 - R:/127.0.0.1:8080] FLUSH
服务器将消息进行解码,虽然之前发出来的时候是黏包了,都是现在输出的是没有黏包的
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] REGISTERED
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] ACTIVE
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 30 30 5f 5f 5f 5f 5f 5f 5f |000_______ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 31 31 31 31 5f 5f 5f 5f 5f |11111_____ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 32 32 32 32 32 32 5f 5f 5f 5f |222222____ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 33 33 33 33 33 5f 5f 5f 5f 5f |33333_____ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 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 5f 5f |44444444__ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 35 35 35 5f 5f 5f 5f 5f 5f 5f |555_______ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 36 36 36 36 36 36 36 5f 5f 5f |6666666___ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 37 37 37 37 37 5f 5f 5f 5f 5f |77777_____ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 38 38 38 38 5f 5f 5f 5f 5f 5f |8888______ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 39 39 5f 5f 5f 5f 5f 5f 5f 5f |99________ |
+--------+-------------------------------------------------+----------------+
23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
第一种方法:
服务端加入,默认以 \n 或 \r\n 作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常
//以换行符作为分隔符 (Linux: \n 和 Windows: \r\n)
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
客户端在每条消息之后,加入 \n 分隔符
第二种方法定义分隔符
/*
maxFrameLength: 最大长度
delimiter: 字节定义的ByteBuf类型的分隔符
*/
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter));
循环十次,发送10条消息,只不过这些消息都会以\n
结结束
public class Client3 {
static final Logger log = LoggerFactory.getLogger(Client1.class);
public static void main(String[] args) {
send();
System.out.println("finish");
}
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.channel(NioSocketChannel.class);
bootstrap.group(worker);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
// 会在连接 channel 建立成功后,会触发 active 事件
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf buf = ctx.alloc().buffer();
char c = '0';
Random r = new Random();
for (int i = 0; i < 10; i++) {
StringBuilder sb = makeString(c, r.nextInt(256) + 1);
c++;
buf.writeBytes(sb.toString().getBytes());
}
ctx.writeAndFlush(buf);
}
});
}
});
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();
}
}
}
@Slf4j
public class Server3 {
void start() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
// 调整系统的接收缓冲区(滑动窗口)
// serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
// 调整 netty 的接收缓冲区(byteBuf)
serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) {
new Server3().start();
}
}
在发送消息前,先约定用定长字节表示接下来数据的长度
// 最大长度,长度偏移,长度占用字节,长度调整,剥离字节数
/*
maxFrameLength: 最大长度
lengthFieldoffset : 长度字段偏移量
lengthFieldLength : 消息长度占用字节
lengthAdjustment : 长度字段为基准,还有几个字节(过后)才是内容
initialBytesToStrip : 从头剥离几个字节, 不需要的字节内容
*/
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 1, 0, 1));
具体的使用,可以看源码中的文档说明
public class TestLengthFieldDecoder {
public static void main(String[] args) {
//使用的是writeInt, int占四个字节
//将版本进行了保留
EmbeddedChannel channel = new EmbeddedChannel(
new LengthFieldBasedFrameDecoder(
1024, 0, 4, 1, 4),
new LoggingHandler(LogLevel.DEBUG)
);
// 4 个字节的内容长度, 实际内容
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
//想buf中加入内容:Hello, world
send(buffer, "Hello, world");
send(buffer, "Hi!");
//将消息写入channel
channel.writeInbound(buffer);
}
private static void send(ByteBuf buffer, String content) {
byte[] bytes = content.getBytes(); // 实际内容
int length = bytes.length; // 实际内容长度
buffer.writeInt(length);
buffer.writeByte(1);//使用一个字节表示版本号为:1
buffer.writeBytes(bytes);
}
}
00:02:23 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] REGISTERED
00:02:23 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] ACTIVE
00:02:23 [DEBUG] [main] i.n.h.l.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| 01 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 |.Hello, world |
+--------+-------------------------------------------------+----------------+
00:02:23 [DEBUG] [main] i.n.h.l.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| 01 48 69 21 |.Hi! |
+--------+-------------------------------------------------+----------------+
00:02:23 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
Process finished with exit code 0