目录
目标
Netty版本
Netty官方API
概述
复现黏包半包现象
黏包案例
半包案例
解决方案
短连接(能解决黏包问题,不能解决半包问题。不推荐。)
定长帧解码器(能解决黏包半包问题。适用于业务数据长度固定的场景。)
分隔符解码器(能解决黏包半包问题。适用于业务数据有固定分隔符的场景。)
基于长度字段的帧解码器(也叫LTC解码器,能解决黏包半包问题。需要将数据的长度写入到ByteBuf。)
了解黏包半包发生的原因,了解各个解决方案适用的场景。
io.netty
netty-all
4.1.87.Final
Netty API Reference (4.1.90.Final)https://netty.io/4.1/api/index.html
黏包(粘包)原因
原因一:业务上,接收方的ByteBuf设置过大(Netty默认1024字节),发送方向接收方发送多条数据,如果数据较小,此时就会出现黏包现象。
原因二:传输层中,TCP采用滑动窗口来进行传输控制,接收方会告诉发送方在某一时刻能发送多少数据(窗口尺寸),当滑动窗口为0时,一般情况下(两种特殊情况这里不做考虑)不能发送数据。实质上就是通过缓冲区达到流量控制的目的。如果滑动窗口远大于发送数据的大小,且接收方有没有及时处理,则缓冲区内就有多个报文,从而产生黏包现象。
原因三:TCP段和IP数据报对会给报文添加首部,如果业务报文较小,首部比业务报文大,此时发送数据会造成流量浪费,因此Nagle算法尽可能多地攒够报文再发送,从而产生黏包现象。
半包原因
原因一:业务上,接收方的ByteBuf设置过小(Netty默认1024字节),发送地数据大于ByteBuf的阈值,则产生半包现象。
原因二:传输层中,TCP传输报文。滑动窗口小于发送数据的大小,则只能截断发送,从而产生半包现象。
原因三:最大报文段长度(MSS)是TCP协议的一个选项,用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度(不包括文段头)。一旦报文超过了MSS,则产生半包现象。
服务端
package com.ctx.sticky;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
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 lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyServer {
public static void main(String[] args) {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast("Handler",new ChannelInboundHandlerAdapter(){
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
}
客户端
package com.ctx.sticky;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
new NettyClient().nb();
}
public void nb() throws InterruptedException {
//启动Netty客户端
Bootstrap bootstrap = new Bootstrap();
//选择EventLoop
bootstrap.group(new NioEventLoopGroup());
//选择客户端Channel实现,Channel是数据的传输通道。
bootstrap.channel(NioSocketChannel.class);
//添加处理器,
bootstrap.handler(new ChannelInitializer() {
//连接建立后初始化Channel
@Override
public void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(
new ChannelInboundHandlerAdapter(){
//与服务端建立连接以后会调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx){
for(int i=0 ;i<3; i++){
ByteBuf bb=ctx.alloc().buffer(16);
byte[] bytes = "Hello World!".getBytes(Charset.forName("UTF-8"));
bb.writeBytes(bytes);
//写入数据
ctx.writeAndFlush(bb);
}
}
}
);
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
channelFuture.sync();
}
}
服务端打印的日志
服务端
package com.ctx.sticky;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyServer {
public static void main(String[] args) {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置系统的接收缓冲区10字节大小(滑动窗口)
serverBootstrap.option(ChannelOption.SO_RCVBUF,10);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast("Handler",new ChannelInboundHandlerAdapter(){
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
}
客户端
package com.ctx.sticky;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
new NettyClient().nb();
}
public void nb() throws InterruptedException {
//启动Netty客户端
Bootstrap bootstrap = new Bootstrap();
//选择EventLoop
bootstrap.group(new NioEventLoopGroup());
//选择客户端Channel实现,Channel是数据的传输通道。
bootstrap.channel(NioSocketChannel.class);
//添加处理器,
bootstrap.handler(new ChannelInitializer() {
//连接建立后初始化Channel
@Override
public void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(
new ChannelInboundHandlerAdapter(){
//与服务端建立连接以后会调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx){
for(int i=0 ;i<3; i++){
ByteBuf bb=ctx.alloc().buffer(16);
byte[] bytes = "Hello World!".getBytes(Charset.forName("UTF-8"));
bb.writeBytes(bytes);
//写入数据
ctx.writeAndFlush(bb);
}
}
}
);
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
channelFuture.sync();
}
}
服务端打印的日志
服务端
package com.ctx.sticky;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyServer {
public static void main(String[] args) {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置接收缓冲区10字节大小,短链接发送方式无法解决半包问题。
//serverBootstrap.option(ChannelOption.SO_RCVBUF,10);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast("Handler",new ChannelInboundHandlerAdapter(){
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
}
客户端
package com.ctx.sticky;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new NettyClient().nb();
}
}
public void nb() throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//启动Netty客户端
Bootstrap bootstrap = new Bootstrap();
//选择EventLoop
bootstrap.group(eventExecutors);
//选择客户端Channel实现,Channel是数据的传输通道。
bootstrap.channel(NioSocketChannel.class);
//添加处理器,
bootstrap.handler(new ChannelInitializer() {
//连接建立后初始化Channel
@Override
public void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
//与服务端建立连接以后会调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf bb = ctx.alloc().buffer(16);
byte[] bytes = "Hello World!".getBytes(Charset.forName("UTF-8"));
bb.writeBytes(bytes);
//写入数据
ctx.writeAndFlush(bb);
ctx.channel().close();
}
}
);
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
channelFuture.sync();
} finally {
//关闭连接
eventExecutors.shutdownGracefully();
}
}
}
服务端打印日志结果
服务端
package com.ctx.sticky;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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 lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyServer {
public static void main(String[] args) {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置接收缓冲区10字节大小。
//serverBootstrap.option(ChannelOption.SO_RCVBUF,10);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
//数据的固定长度为12个字节。
nioSocketChannel.pipeline().addLast(new FixedLengthFrameDecoder(12));
nioSocketChannel.pipeline().addLast("Handler",new ChannelInboundHandlerAdapter(){
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
}
客户端
package com.ctx.sticky;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
new NettyClient().nb();
}
public void nb() throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//启动Netty客户端
Bootstrap bootstrap = new Bootstrap();
//选择EventLoop
bootstrap.group(eventExecutors);
//选择客户端Channel实现,Channel是数据的传输通道。
bootstrap.channel(NioSocketChannel.class);
//添加处理器,
bootstrap.handler(new ChannelInitializer() {
//连接建立后初始化Channel
@Override
public void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
//与服务端建立连接以后会调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 3; i++) {
ByteBuf bb = ctx.alloc().buffer(16);
byte[] bytes = "Hello World!".getBytes(Charset.forName("UTF-8"));
bb.writeBytes(bytes);
//写入数据
ctx.writeAndFlush(bb);
}
ctx.channel().close();
}
}
);
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
channelFuture.sync();
} finally {
//关闭连接
eventExecutors.shutdownGracefully();
}
}
}
服务端打印日志结果
服务端
package com.ctx.sticky;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyServer {
public static void main(String[] args) {
new NettyServer().zdyDecoder();
}
/**
* 自定义分隔符解码器
*/
public void zdyDecoder() {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置接收缓冲区10字节大小。
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
//数据超过20个字节仍然没有遇到##,则报错:io.netty.handler.codec.TooLongFrameException
ByteBuf bb = nioSocketChannel.alloc().buffer();
bb.writeBytes("##".getBytes(Charset.forName("UTF-8")));
nioSocketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(20,bb));
nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
/**
* 行解码器:即以换行符为分隔符来分割消息。
*/
public void lineDecoder() {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置接收缓冲区10字节大小。
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
//数据超过20个字节仍然没有遇到换行符,则报错:io.netty.handler.codec.TooLongFrameException
nioSocketChannel.pipeline().addLast(new LineBasedFrameDecoder(20));
nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
}
客户端
package com.ctx.sticky;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
new NettyClient().nb();
}
public void nb() throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//启动Netty客户端
Bootstrap bootstrap = new Bootstrap();
//选择EventLoop
bootstrap.group(eventExecutors);
//选择客户端Channel实现,Channel是数据的传输通道。
bootstrap.channel(NioSocketChannel.class);
//添加处理器,
bootstrap.handler(new ChannelInitializer() {
//连接建立后初始化Channel
@Override
public void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
//与服务端建立连接以后会调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 3; i++) {
ByteBuf bb = ctx.alloc().buffer(16);
byte[] bytes = "Hello World!##".getBytes(Charset.forName("UTF-8"));
bb.writeBytes(bytes);
//写入数据
ctx.writeAndFlush(bb);
}
ctx.channel().close();
}
}
);
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
channelFuture.sync();
} finally {
//关闭连接
eventExecutors.shutdownGracefully();
}
}
}
服务端打印日志结果
服务端
package com.ctx.sticky;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyServer {
public static void main(String[] args) {
new NettyServer().lengthFieldOffsetTest();
}
/**
* 基于长度字段的帧解码器
* 该方法演示:initialBytesToStrip字段作用。
* initialBytesToStrip:要剥离的初始字节,比如:设置的数据长度用的是int,则可以填写4表示剥离4个字节的长度。设置为0表示不剥离。
*/
public void initialBytesToStripTest() {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置接收缓冲区10字节大小。
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
/**
* maxFrameLength:最大的消息长度
* lengthFieldOffset:
* lengthFieldLength:设定消息长度所占用的字节数,比如:int类型占用四个字节。
* lengthAdjustment:
* initialBytesToStrip:要剥离的初始字节,比如:设置的数据长度用的是int,则可以填写4表示剥离4个字节的长度。设置为0表示不剥离。
*
*/
nioSocketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(
512,0,4,0,4
));
nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
/**
* 基于长度字段的帧解码器
* 该方法演示:lengthAdjustment字段作用。
* lengthAdjustment:调整长度,比如在ByteBuf加入消息之前加入了其他数据,如:版本信息等。此时可以通过该参数设定附加数据的长度,达到调整整体数据长度的目的。
*/
public void lengthAdjustmentTest() {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置接收缓冲区10字节大小。
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
/**
* maxFrameLength:最大的消息长度
* lengthFieldOffset:
* lengthFieldLength:设定消息长度所占用的字节数,比如:int类型占用四个字节。
* lengthAdjustment:调整长度,比如在ByteBuf加入消息之前加入了其他数据,如:版本信息等。此时可以通过该参数设定附加数据的长度,达到调整整体数据长度的目的。
* initialBytesToStrip:要剥离的初始字节,比如:设置的数据长度用的是int,则可以填写4表示剥离4个字节的长度。设置为0表示不剥离。
*
*/
nioSocketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(
512,0,4,3,7
));
nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
/**
* 基于长度字段的帧解码器
* 该方法演示:lengthAdjustment字段作用。
* lengthFieldOffset:长度字段偏移。如果消息长度内容前面有其他内容,可以通过该参数设置截取消息长度的起始位置。
*/
public void lengthFieldOffsetTest() {
//Netty服务端启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
//选择服务器的ServerSocketChannel实现,这里我选择NIO。
serverBootstrap.channel(NioServerSocketChannel.class);
//设置接收缓冲区10字节大小。
serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
//设置事件类型
serverBootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
/**
* maxFrameLength:最大的消息长度
* lengthFieldOffset:长度字段偏移。如果消息长度内容前面有其他内容,可以通过该参数设置截取消息长度的起始位置。
* lengthFieldLength:设定消息长度所占用的字节数,比如:int类型占用四个字节。
* lengthAdjustment:调整长度,比如在ByteBuf加入消息之前加入了其他数据,如:版本信息等。此时可以通过该参数设定附加数据的长度,达到调整整体数据长度的目的。
* initialBytesToStrip:要剥离的初始字节,比如:设置的数据长度用的是int,则可以填写4表示剥离4个字节的长度。设置为0表示不剥离。
*
*/
nioSocketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(
512,4,4,3,11
));
nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
//处理读事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
log.info(s);
}
});
}
});
//设置服务器的监听端口
serverBootstrap.bind(8999);
}
}
客户端
package com.ctx.sticky;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
new NettyClient().lengthFieldOffsetTestTest();
}
public void initialBytesToStripTest() throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//启动Netty客户端
Bootstrap bootstrap = new Bootstrap();
//选择EventLoop
bootstrap.group(eventExecutors);
//选择客户端Channel实现,Channel是数据的传输通道。
bootstrap.channel(NioSocketChannel.class);
//添加处理器,
bootstrap.handler(new ChannelInitializer() {
//连接建立后初始化Channel
@Override
public void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
//与服务端建立连接以后会调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf bb = ctx.alloc().buffer(1024);
byte[] bytes = "I love you three thousand times a day.".getBytes(Charset.forName("UTF-8"));
//写入这条完整数据的长度。
bb.writeInt(bytes.length);
//写入这条消息的内容。
bb.writeBytes(bytes);
byte[] bytes2 = "Avengers Alliance Rally.".getBytes(Charset.forName("UTF-8"));
//写入这条完整数据的长度。
bb.writeInt(bytes2.length);
//写入这条消息的内容。
bb.writeBytes(bytes2);
//写入数据
ctx.writeAndFlush(bb);
ctx.channel().close();
}
}
);
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
channelFuture.sync();
} finally {
//关闭连接
eventExecutors.shutdownGracefully();
}
}
public void lengthAdjustmentTest() throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//启动Netty客户端
Bootstrap bootstrap = new Bootstrap();
//选择EventLoop
bootstrap.group(eventExecutors);
//选择客户端Channel实现,Channel是数据的传输通道。
bootstrap.channel(NioSocketChannel.class);
//添加处理器,
bootstrap.handler(new ChannelInitializer() {
//连接建立后初始化Channel
@Override
public void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
//与服务端建立连接以后会调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf bb = ctx.alloc().buffer(1024);
byte[] bytes = "I love you three thousand times a day.".getBytes(Charset.forName("UTF-8"));
//写入这条完整数据的长度。
bb.writeInt(bytes.length);
//版本号
bb.writeBytes("1.0".getBytes(Charset.forName("UTF-8")));
//写入这条消息的内容。
bb.writeBytes(bytes);
byte[] bytes2 = "Avengers Alliance Rally.".getBytes(Charset.forName("UTF-8"));
//写入这条完整数据的长度。
bb.writeInt(bytes2.length);
//版本号
bb.writeBytes("1.0".getBytes(Charset.forName("UTF-8")));
System.out.println("1.0".getBytes(Charset.forName("UTF-8")).length);
//写入这条消息的内容。
bb.writeBytes(bytes2);
//写入数据
ctx.writeAndFlush(bb);
ctx.channel().close();
}
}
);
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
channelFuture.sync();
} finally {
//关闭连接
eventExecutors.shutdownGracefully();
}
}
public void lengthFieldOffsetTestTest() throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//启动Netty客户端
Bootstrap bootstrap = new Bootstrap();
//选择EventLoop
bootstrap.group(eventExecutors);
//选择客户端Channel实现,Channel是数据的传输通道。
bootstrap.channel(NioSocketChannel.class);
//添加处理器,
bootstrap.handler(new ChannelInitializer() {
//连接建立后初始化Channel
@Override
public void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
//与服务端建立连接以后会调用该方法
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf bb = ctx.alloc().buffer(1024);
byte[] bytes = "I love you three thousand times a day.".getBytes(Charset.forName("UTF-8"));
//加入额外的信息
bb.writeBytes("cafe".getBytes(Charset.forName("UTF-8")));
//写入这条完整数据的长度。
bb.writeInt(bytes.length);
//版本号
bb.writeBytes("1.0".getBytes(Charset.forName("UTF-8")));
//写入这条消息的内容。
bb.writeBytes(bytes);
byte[] bytes2 = "Avengers Alliance Rally.".getBytes(Charset.forName("UTF-8"));
//加入额外的信息
bb.writeBytes("cafe".getBytes(Charset.forName("UTF-8")));
//写入这条完整数据的长度。
bb.writeInt(bytes2.length);
//版本号
bb.writeBytes("1.0".getBytes(Charset.forName("UTF-8")));
System.out.println("1.0".getBytes(Charset.forName("UTF-8")).length);
//写入这条消息的内容。
bb.writeBytes(bytes2);
//写入数据
ctx.writeAndFlush(bb);
ctx.channel().close();
}
}
);
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
channelFuture.sync();
} finally {
//关闭连接
eventExecutors.shutdownGracefully();
}
}
}