首先来看简单的项目结构
Netty5的Maven包
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-codec-http -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>5.0.0.Alpha1</version>
</dependency>
ClientHandler类
public class ClientHandler extends SimpleChannelInboundHandler {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String str = "hello";
for(int i = 0; i < 1000; i++){
ctx.channel().writeAndFlush(str);
}
super.channelActive(ctx);
}
@Override
protected void messageReceived(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
}
}
HelloHandler类
public class HelloHandler extends SimpleChannelInboundHandler<String> {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exceptionCaught");
cause.printStackTrace();
super.exceptionCaught(ctx, cause);
}
@Override
protected void messageReceived(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
System.out.println("messageReceived:" + msg);
}
}
NettyClient类
public class NettyClient {
public static void main(String[] args) {
Bootstrap bootStrap = new Bootstrap();
EventLoopGroup worker = new NioEventLoopGroup();
bootStrap.group(worker);
try{
bootStrap.channel(NioSocketChannel.class);
bootStrap.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast("StringEncoder", new StringEncoder());
channel.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture channelFuture = bootStrap.connect("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
}finally {
worker.shutdownGracefully();
}
}
}
NettyServer类
public class NettyServer {
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try{
bootstrap.group(boss, worker);
//设置ServerSocketChannel工厂类
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast("StringEncoder", new StringEncoder());
ch.pipeline().addLast("StringDecoder", new StringDecoder());
ch.pipeline().addLast("HelloHandler", new HelloHandler());
}
});
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
ChannelFuture channelFuture = bootstrap.bind(8080).sync();
System.out.println("server start...");
Channel channel = channelFuture.channel();
//优雅关闭服务
channel.closeFuture().sync();
}catch (Exception e){
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
重点关注ClientHandler类里面,给服务器发送了1000个hello字符串
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String str = "hello";
for(int i = 0; i < 1000; i++){
ctx.channel().writeAndFlush(str);
}
super.channelActive(ctx);
}
先启动服务器,再启动客户端
可以看到有些hello单独发出去,有些hello则"粘"在一起发出去
可以看到,有些hello字符粘在一起发送
解决方案1:固定长度的拆包器 FixedLengthFrameDecoder
在服务器端加上FixedLengthFrameDecoder方法,并设置长度,使服务器达到一定长度就从TCP缓冲区获取数据包
但是我们这里的的长度设置的和hello长度一样,所以看起来是解决了粘包问题,但是在聊天程序里面,客户端发送的信息长度服务器事先并不知道,所以这并不适合在聊天应用中使用
bootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new FixedLengthFrameDecoder(5));
ch.pipeline().addLast("StringEncoder", new StringEncoder());
ch.pipeline().addLast("StringDecoder", new StringDecoder());
ch.pipeline().addLast("HelloHandler", new HelloHandler());
}
});
解决方案2:将特殊分隔符作为消息分隔符
每个应用层数据包,都通过自定义的分隔符,进行分割拆分
服务端加上DelimiterBasedFrameDecoder并设置 “\t” 为分隔符
bootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ByteBuf split = Unpooled.copiedBuffer("\t".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, split));
ch.pipeline().addLast("StringEncoder", new StringEncoder());
ch.pipeline().addLast("StringDecoder", new StringDecoder());
ch.pipeline().addLast("HelloHandler", new HelloHandler());
}
});
ClientHandler类消息的发送末尾加上分隔符:"\t"
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String str = "hello";
for (int i = 0; i < 1000; i++) {
ctx.channel().writeAndFlush(str + "\t");
}
super.channelActive(ctx);
}