netty(5)--粘包解决

这一节我们用的是netty5的版本,但使用的是netty4的API去实现的代码

  • Server
package com.xiyou.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class Server {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("服务器端已经启动....");
        // 1.创建2个线程,一个负责接收客户端连接, 一个负责进行 传输数据
        NioEventLoopGroup pGroup = new NioEventLoopGroup();
        NioEventLoopGroup cGroup = new NioEventLoopGroup();
        // 2. 创建服务器辅助类
        ServerBootstrap b = new ServerBootstrap();
        b.group(pGroup, cGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024)
                // 3.设置缓冲区与发送区大小
                .option(ChannelOption.SO_SNDBUF, 32 * 1024).option(ChannelOption.SO_RCVBUF, 32 * 1024)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        sc.pipeline().addLast(new StringDecoder());
                        sc.pipeline().addLast(new StringEncoder());
                        sc.pipeline().addLast(new ServerHandler());
                    }
                });
        ChannelFuture cf = b.bind(10101).sync();
        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
        cGroup.shutdownGracefully();

    }

}
  • ServerHandler
package com.xiyou.server;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

class ServerHandler extends ChannelHandlerAdapter {
    /**
     * 当通道被调用,执行该方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 接收数据
        String value = (String) msg;
        System.out.println("from client msg:" + value);
        // 回复给客户端 “您好!”
        String res = "I am ok...";
        ctx.writeAndFlush(res);
    }

}
  • Client
package com.xiyou.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Client {

    public static void main(String[] args) throws Exception {
        System.out.println("客户端已经启动....");
        // 创建负责接收客户端连接
        NioEventLoopGroup pGroup = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(pGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel sc) throws Exception {
                sc.pipeline().addLast(new StringDecoder());
                sc.pipeline().addLast(new StringEncoder());
                sc.pipeline().addLast(new ClientHandler());
            }
        });
        ChannelFuture cf = b.connect("127.0.0.1", 10101).sync();
        // 关闭端口号,否则客户端程序没法退出
        // cf.channel().close();
        cf.channel().writeAndFlush("one");
        cf.channel().writeAndFlush("two");
        cf.channel().writeAndFlush("three");
        Thread.sleep(1000);
        cf.channel().writeAndFlush("four");
        cf.channel().writeAndFlush("five");
        // 等待客户端端口号关闭
        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
        System.out.println("已经关闭了");
    }
}
  • ClientHandler
package com.xiyou.client;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

class ClientHandler extends ChannelHandlerAdapter {

    /**
     * 当通道被调用,执行该方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 接收数据
        String value = (String) msg;
        System.out.println("from server msg:" + value);
    }
}

上面的代码我们看下,客户端发送给服务端的时候直接是连着写了五个发送数据的方法,中间Thread.sleep(1000)睡眠一秒、

		cf.channel().writeAndFlush("one");
        cf.channel().writeAndFlush("two");
        cf.channel().writeAndFlush("three");
        Thread.sleep(1000);
        cf.channel().writeAndFlush("four");
        cf.channel().writeAndFlush("five");

我们在开启服务端之后,启动客户端,可以发现,服务端的接受信息是

服务器端已经启动....
from client msg:onetwothree
from client msg:fourfive
上面的现象表示了,其实客户端只是发送了两次请求给服务端,一次是Thread.sleep之前的,一次是之后,将信息一起发送,而不是单条发送。这是为什么呢?

上面只发送两次的原因其实是因为粘包导致的。

  • 粘包
    将多个包合在一起进行发送,其实也就是将多个连续发送的信息放在一起,一块发送。
  • 拆包
    将一个包拆分成多个包
  • 粘包与拆包的场景只会在TCP中出现,而不会再UDP中出现。

上面造成粘包的原因是发送的数据速度过快,netty进行优化,将多个数据放到同一个包中进行数据打包发送,因此将休眠之前的数据打包发送,将休眠之后的数据打包发送。netty这么做的目的就是减轻服务端的压力,无需重新建立连接请求,重新建立channel通道。

粘包其实是看服务器的,无法具体确定可以同时发送多少个。

如何解决粘包问题?

如果我们不想发生粘包现象,应该怎么做?

  • 定义消息长度
    服务器规定了接受的长度,用接收的长度进行控制(不常用,不推荐使用,如果发送的当前数据过大,无法处理)
  • 将消息分为消息头和消息体,消息头中包含表示信息的总长度(或消息体的长度)的字段。
    该方法使用的较多
  • 可以设定规定的字符进行拆包处理:(我们推荐用这种)
    (1)设定过滤器的时候加入过滤器
ByteBuf buf =  Unpooled.copiedBuffer("_mayi".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));

发送的数据应该是:

channelFuture.channel().writeAndFlush("hello_mayi");

发送的时候以_mayi 为后缀进行发送,如果发送的数据没有_mayi 则无法正常接收数据。因为没有这个规定的分割符号,则会在通道中一直被阻塞,所以无法发送到服务端。

该方法的作用是只要我们找到了_mayi的字符串,我们就对其内容进行发送,如果没有找到,我们就会进行等待
(2)应该同时在服务端和客户端都添加该代码

  • 还可以用长度去解决粘包问题,但是该方法不好,因为如果发送的数据小于规定的长度不会对其进行发送。发送的数据如果过长,就是只要到达规定长度就进行发送。

你可能感兴趣的:(NIO,netty,架构师之道)