1.消息定长
2.在包尾都增加特殊字符进行分割
3.将消息分为消息头和消息体
针对这三种方法,下面我会分别举例验证
对应第一种解决方法:消息定长
(1)例1:服务端代码:
public class Server4 {
public static void main(String[] args) throws SigarException {
//boss线程监听端口,worker线程负责数据读写
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try{
//辅助启动类
ServerBootstrap bootstrap = new ServerBootstrap();
//设置线程池
bootstrap.group(boss,worker);
//设置socket工厂
bootstrap.channel(NioServerSocketChannel.class);
//设置管道工厂
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline pipeline = socketChannel.pipeline();
//定长解码类
pipeline.addLast(new FixedLengthFrameDecoder(19));
//字符串解码类
pipeline.addLast(new StringDecoder());
//处理类
pipeline.addLast(new ServerHandler4());
}
});
//绑定端口
ChannelFuture future = bootstrap.bind(8866).sync();
System.out.println("server start ...... ");
//等待服务端监听端口关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//优雅退出,释放线程池资源
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
class ServerHandler4 extends SimpleChannelInboundHandler <String>{
//用于记录次数
private int count = 0;
//读取客户端发送的数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("RESPONSE--------"+msg+";"+" @ "+ ++count);
}
//新客户端接入
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
//客户端断开
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
}
//异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭通道
ctx.channel().close();
//打印异常
cause.printStackTrace();
}
}
(2)例1:客户端代码:
public class Client4 {
public static void main(String[] args) {
//worker负责读写数据
EventLoopGroup worker = new NioEventLoopGroup();
try {
//辅助启动类
Bootstrap bootstrap = new Bootstrap();
//设置线程池
bootstrap.group(worker);
//设置socket工厂
bootstrap.channel(NioSocketChannel.class);
//设置管道
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline pipeline = socketChannel.pipeline();
//定长解码类
pipeline.addLast(new FixedLengthFrameDecoder(19));
//字符串编码类
pipeline.addLast(new StringEncoder());
//处理类
pipeline.addLast(new ClientHandler4());
}
});
//发起异步连接操作
ChannelFuture futrue = bootstrap.connect(new InetSocketAddress("127.0.0.1",8866)).sync();
//等待客户端链路关闭
futrue.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅的退出,释放NIO线程组
worker.shutdownGracefully();
}
}
}
class ClientHandler4 extends SimpleChannelInboundHandler<String> {
//接受服务端发来的消息
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("server response : "+msg);
}
//与服务器建立连接
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//给服务器发消息
String s = System.getProperty("line.separator");
//发送50次消息
for (int i = 0; i < 50; i++) {
ctx.channel().writeAndFlush(" I am client "+s);
}
}
//与服务器断开连接
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
}
//异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭管道
ctx.channel().close();
//打印异常信息
cause.printStackTrace();
}
}
例1服务端运行结果:
…………………….此处省略多行………………….
分析:从运行结果可以看出,符合我们的预期,并没有TCP粘包问题,这是因为使用的定长解码器的原因,我在此解释一下例1client/server代码中新增加了几个“陌生”的类,若之后再次出现,则不作解释!
对应第二种解决方法:在包尾都增加特殊字符(行分隔符)进行分割
(1)例2:服务端代码:
public class Server4 {
public static void main(String[] args) throws SigarException {
//boss线程监听端口,worker线程负责数据读写
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try{
//辅助启动类
ServerBootstrap bootstrap = new ServerBootstrap();
//设置线程池
bootstrap.group(boss,worker);
//设置socket工厂
bootstrap.channel(NioServerSocketChannel.class);
//设置管道工厂
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline pipeline = socketChannel.pipeline();
//行分隔符解码类
pipeline.addLast(new LineBasedFrameDecoder(1024));
//字符串解码类
pipeline.addLast(new StringDecoder());
//处理类
pipeline.addLast(new ServerHandler4());
}
});
//绑定端口
ChannelFuture future = bootstrap.bind(8866).sync();
System.out.println("server start ...... ");
//等待服务端监听端口关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//优雅退出,释放线程池资源
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
class ServerHandler4 extends SimpleChannelInboundHandler <String>{
//用于记录次数
private int count = 0;
//读取客户端发送的数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("RESPONSE--------"+msg+";"+" @ "+ ++count);
}
//新客户端接入
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
//客户端断开
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
}
//异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭通道
ctx.channel().close();
//打印异常
cause.printStackTrace();
}
}
(2)例2:客户端代码:
public class Client4 {
public static void main(String[] args) {
//worker负责读写数据
EventLoopGroup worker = new NioEventLoopGroup();
try {
//辅助启动类
Bootstrap bootstrap = new Bootstrap();
//设置线程池
bootstrap.group(worker);
//设置socket工厂
bootstrap.channel(NioSocketChannel.class);
//设置管道
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline pipeline = socketChannel.pipeline();
//行分隔符解码类
pipeline.addLast(new LineBasedFrameDecoder(1024));
//字符串编码类
pipeline.addLast(new StringEncoder());
//处理类
pipeline.addLast(new ClientHandler4());
}
});
//发起异步连接操作
ChannelFuture futrue = bootstrap.connect(new InetSocketAddress("127.0.0.1",8866)).sync();
//等待客户端链路关闭
futrue.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅的退出,释放NIO线程组
worker.shutdownGracefully();
}
}
}
class ClientHandler4 extends SimpleChannelInboundHandler<String> {
//接受服务端发来的消息
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("server response : "+msg);
}
//与服务器建立连接
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//给服务器发消息
String s = System.getProperty("line.separator");
//发送50次消息
for (int i = 0; i < 50; i++) {
ctx.channel().writeAndFlush(" I am client "+s);
}
}
//与服务器断开连接
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
}
//异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭管道
ctx.channel().close();
//打印异常信息
cause.printStackTrace();
}
}
例2服务端运行结果:
…………………….此处省略多行………………….
分析:从运行结果可以看出没有TCP粘包问题了,细心的你或许已经发现代码中新出现了一个LineBasedFrameDecoder类,它可以携带参数,我指定的参数为1024,含义为在每1024个字节中寻找换行符,若有,就发送消息,否则继续寻找。
对应第二种解决方法:在包尾都增加特殊字符(#)进行分割
例3:服务端代码,和例2服务端代码类似,由于篇幅有限,我就仅仅指出它们不一样的地方了!
将例2服务端 第23行 和第24行 代码修改为
//自定义分隔符解码类
pipeline.addLast(newDelimiterBasedFrameDecoder(1024,Unpooled.copiedBuffer("#".getBytes())));
例3:客户端代码:(和例2客户端代码的不同之处)
将例2客户端 第24行 和第25行 代码修改为
//自定义分隔符解码类
pipeline.addLast(newDelimiterBasedFrameDecoder(1024,Unpooled.copiedBuffer("#".getBytes())));
再将例2客户端 第63行 代码修改为
ctx.channel().writeAndFlush(" I am client "+"#");
例3服务端运行结果:
…………………….此处省略多行………………….
分析:从运行结果可以看出TCP粘包问题解决了!代码中新出现了一个DelimiterBasedFrameDecoder类,它可以携带参数,我指定的参数为(1024,Unpooled.copiedBuffer(“#”.getBytes()))),含义为在每1024个字节中寻找#,若有,就发送消息,否则继续寻找。
对应第三种方法:将消息分为消息头和消息体
对于消息头和消息体,有三种情况分别如下:
有头部的拆包与粘包:
lengthFieldOffset = 2 长度字段偏移量 ( = 外部头部Header 1的长度)
lengthFieldLength = 3 长度字段占用字节数
lengthAdjustment = 0
initialBytesToStrip = 0
长度字段在前且有头部的拆包与粘包:
lengthFieldOffset = 0 长度字段偏移量
lengthFieldLength = 3 长度字段占用字节数
lengthAdjustment = 2 ( Header 1 的长度)
initialBytesToStrip = 0
举一个简单的例子
例4:
import netty.EnDecode.Request;
/** * 请求解码器 * <pre> * 数据包格式 * +——----——+——-----——+——----——+——----——+——-----——+ * | 包头 | 模块号 | 命令号 | 长度 | 数据 | * +——----——+——-----——+——----——+——----——+——-----——+ * </pre> * 包头4字节 * 模块号2字节short * 命令号2字节short * 长度4字节(描述数据部分字节长度) */
public class MyRequestDecoder extends FrameDecoder{
//数据包基本长度
public static final int BASE_LENTH = 4 + 2 + 2 + 4;
@Override
protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception {
//可读长度必须大于基本长度
if(buffer.readableBytes() >= BASE_LENTH){
//防止socket字节流攻击
if(buffer.readableBytes() > 2048){
buffer.skipBytes(buffer.readableBytes());
}
//记录包头开始的index
int beginReader;
while(true){
beginReader = buffer.readerIndex();
buffer.markReaderIndex();
if(buffer.readInt() == -32523523){
break;
}
//未读到包头,略过一个字节
buffer.resetReaderIndex();
buffer.readByte();
//长度又变得不满足
if(buffer.readableBytes() < BASE_LENTH){
return null;
}
}
//模块号
short module = buffer.readShort();
//命令号
short cmd = buffer.readShort();
//长度
int length = buffer.readInt();
//判断请求数据包数据是否到齐
if(buffer.readableBytes() < length){
//还原读指针
buffer.readerIndex(beginReader);
return null;
}
//读取data数据
byte[] data = new byte[length];
buffer.readBytes(data);
Request request = new Request();
request.setModule(module);
request.setCmd(cmd);
request.setData(data);
//继续往下传递
return request;
}
//数据包不完整,需要等待后面的包来
return null;
}
}
本人才疏学浅,如有错误,请指出
谢谢!