Netty高级(Netty5.0的用法,TCP粘包、拆包问题解决方案)
Netty5案例展示
Maven坐标
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jboss.marshalling/jboss-marshalling -->
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling</artifactId>
<version>1.3.19.GA</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jboss.marshalling/jboss-marshalling-serial -->
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling-serial</artifactId>
<version>1.3.18.GA</version>
<scope>test</scope>
</dependency>
服务器端
public class Netty4Server {
public static void main(String[] args) throws InterruptedException {
System.out.println("Netty4服务器端已经启动....");
//创建两个线程,一个负责接受客户端连接 一个负责传输数据
NioEventLoopGroup pGroup = new NioEventLoopGroup();
NioEventLoopGroup cGroup = new NioEventLoopGroup();
//2.创建服务器辅助类
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(pGroup,cGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024)
//设置缓冲区与发送区大小
.option(ChannelOption.SO_SNDBUF,32*1024).option(ChannelOption.SO_RCVBUF,32*1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new Server4Handler());
}
});
ChannelFuture cf = serverBootstrap.bind(8080).sync();
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
class Server4Handler extends ChannelHandlerAdapter{
/**
* 当通道被调用时,执行该方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接受客户端数据
String value=(String) msg;
System.out.println("Client Msg:"+value);
//回复给客户端
ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("您好,主人暂时不在,有事请您留言。".getBytes()));
}
}
客户端
public class Netty4Client {
public static void main(String[] args) throws InterruptedException {
System.out.println("Netty4客户端已经启动...");
NioEventLoopGroup pGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(pGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new Client4Handler());
}
});
ChannelFuture cf = bootstrap.connect("127.0.0.1", 8080).sync();
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("yswKnight".getBytes()));
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("wangyongsheng".getBytes()));
Thread.sleep(500);//如果在这不停一下,则会让两个消息连在一起发送出去!
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("LebronJames".getBytes()));
//等待客户端端口号被关闭
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
}
}
class Client4Handler extends ChannelHandlerAdapter{
/**
* 当通道被调用时,执行该方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接受消息
String value=(String)msg;
System.out.println("Server Msg:"+value);
}
}
运行结果
服务端:
客户端:
TCP粘包、拆包问题解决方案
什么是粘包/拆包
一个完整的业务可能会被TCP拆分成多个包进行发送(拆包),也有可能把多个小的包封装成一个大的数据包发送(粘包),
这个就是TCP的拆包和封包问题。
下面可以看一张图,是客户端向服务端发送包:
解析上图:
1. 第一种情况,Data1和Data2都分开发送到了Server端,没有产生粘包和拆包的情况。
2. 第二种情况,Data1和Data2数据粘在了一起,打成了一个大的包发送到Server端,这个情况就是粘包。
3. 第三种情况,Data2被分离成Data2_1和Data2_2,并且Data2_1在Data1之前到达了服务端,这种情况就产生了拆包。
由于网络的复杂性,可能数据会被分离成N多个复杂的拆包/粘包的情况,所以在做TCP服务器的时候就需要首先解决拆包/粘包的问题。
解决办法
方法一:
消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定, 这样即使粘包了通过接收方编程实现获取定长报文也能区分。
socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(10));
1
案例
其他代码不变,在此处加入这行代码!
服务端:
客户端:
运行结果
服务端:
客户端:
总结
从结果可以看出,TCP将消息报文 以 每10个字节 拆开发送,但这种方法不推荐使用,因为消息长度都是固定的,读取消息时的效率特别低!
方法二:
报文添加特殊分隔符,
例如每条报文结束都添加回车换行符(例如FTP协议) 或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
ByteBuf buf = Unpooled.copiedBuffer("_split".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
案例
其他代码相似,注意红圈!
服务端:
客户端:
运行结果
服务端:
客户端:
总结
从结果可以看出,按照我在消息中自定义的分隔符“_split“,将消息报文分别拆开发送;
注意:如果不写分隔符,消息将不会被发送出去!
方法三:
将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段。
---------------------
作者:詠聖wK
来源:****
原文:https://blog.****.net/yswKnight/article/details/79363668
版权声明:本文为博主原创文章,转载请附上博文链接!