微服务架构-高性能Netty服务器-063:Netty实战与tcp协议粘包与拆包解决方案

1 回顾NIO核心设计思想和理念

课程内容:
1.回顾NIO的核心设计思想是什么?
2.为什么放弃原生Nio选用Netty框架
3.基于Netty实现客户端与服务器端通讯
4.解决tcp协议粘包与拆包解决方案

Nio核心设计思想:非阻塞式 、(选择器)io多路复用原则、缓冲区(提高读写效率)

2 为什么放弃NIO使用Netty框架

不选择Java原生NIO编程的原因
1.NIO的类库和API繁杂,使用麻烦;
2.需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程;
3.可靠性能力补齐,工作量和难度都非常大;

为什么选择Netty框架
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架Avro就使用了Netty作为底层通信框架,其他还有业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。

3 Netty线程模型设计思想

创建一个Netty项目
Maven依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.42.Final</version>
</dependency>

在这里插入图片描述
使用netty创建服务器端的时候,采用两个线程池
boss线程池 负责接收请求
work线程池 处理请求读写操作

4 基于Netty创建服务器端

Netty服务器端

public class ServerHandler extends SimpleChannelInboundHandler {
    /**
     * 获取数据
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);
        // 相应代码
        ctx.writeAndFlush(Unpooled.copiedBuffer("平均月薪突破3w", CharsetUtil.UTF_8));
    }
}
public class NettyServer {

    private static int inetPort = 8080;

    public static void main(String[] args) {
        // 使用netty创建服务器端的时候,采用两个线程池
        // boss线程池 负责接收请求
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        // work线程池 处理请求读写操作
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        // 创建serverBootstrap
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // serverSocketChannel管理多个socketChannel
        // NioServerSocketChannel标记当前为服务器端
        serverBootstrap.group(workGroup, workGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 处理每个请求handler
                        sc.pipeline().addLast(new ServerHandler());
                    }
                });
        try {
            // 绑定端口号
            ChannelFuture channelFuture = serverBootstrap.bind(inetPort).sync();
            System.out.println("服务器端启动成功:" + inetPort);
            // 等待监听请求
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

Socket客户端

public class SocketClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("请问每特教育平均月薪突破多少?".getBytes());
        // 长连接防止Server端报错
        while(true){}
//        outputStream.close();
//        socket.close();
    }
}

测试结果:
在这里插入图片描述

5 基于Netty创建客户端

Netty客户端

public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {


    /**
     * 活跃通道可以发送消息
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 发送数据
        ctx.writeAndFlush(Unpooled.copiedBuffer("每特教育平均月薪突破多少?", CharsetUtil.UTF_8));
    }

    /**
     * 读取消息
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        String resp = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("客户端获取服务器端相应:" + resp);
    }
}
public class NettyClient {
    public static void main(String[] args) {
        //创建nioEventLoopGroup
        NioEventLoopGroup group = new NioEventLoopGroup();
         Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group).channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ClientHandler());
                    }
                });
        try {
            // 发起同步连接
            ChannelFuture sync = bootstrap.connect().sync();
            sync.channel().closeFuture().sync();
        } catch (Exception e) {

        } finally {
            group.shutdownGracefully();
        }
    }
}

测试结果:
在这里插入图片描述

6 什么是粘包、拆包基本概念

什么是粘包、拆包
粘包:多次发送的消息,客户端一次合并读取 msg+msg
Msg Msg=msgmsg
拆包:第一次完整消息+第二次部分消息组合、第二次剩下部分的消息
Msg Msg=MsgM sg

原因的造成:
因为现在的tcp连接默认以长连接的形式实现通讯,发送请求之后不会立马关闭连接。
客户端与服务器端建立连接,客户端发送一条消息,客户端与服务器端关闭连接,不存在粘包拆包问题;
客户端与服务器端建立连接,客户端发送多条消息,客户端与服务器端关闭连接,可能存在粘包拆包问题;

7 解决tcp协议粘包拆分思路

为什么会造成粘包与拆包? Tcp和缓冲区造成
Tcp协议为了能够高性能传输,发送和接收采用缓冲区
在这里插入图片描述
1.当客户端发送的数据消息<服务器端读取的缓冲区大小,会发生粘包;
2.当客户端发送的数据消息>服务器端读取的缓冲区大小,会发送拆包;
3.服务端不能够及时的获取缓冲区的数据,也会产生粘包的问题;
解决粘和拆包思路:
1.以固定的长度发送数据到缓冲区
2.可以在数据之间设置边界 \n

8 使用LineBaseDFrameDecoder解决粘包

利用编码器LineBaseDFrameDecoder解决tcp粘包的问题

serverBootstrap.group(workGroup, workGroup).channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel sc) throws Exception {
                // 处理每个请求handler
                // 对发送数据设置边界
                sc.pipeline().addLast(new LineBasedFrameDecoder(1024));
                sc.pipeline().addLast(new StringEncoder());
                sc.pipeline().addLast(new ServerHandler());
            }
        });

对应写的语句加上\n或者\r\n
测试结果:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值