63.netty-tcp协议粘包与拆包解决方案

1. NIO的核心组件

管道 选择器 缓冲区
在这里插入图片描述
一个线程处理多个不同的io操作

通道(Channel)
通常我们nio所有的操作都是通过通道开始的,所有的通道都会注册到统一个选择器(Selector)上实现管理,在通过选择器将数据统一写入到 buffer中。

缓冲区(Buffer)
Buffer本质上就是一块内存区,可以用来读取数据,也就先将数据写入到缓冲区中、在统一的写入到硬盘上。

选择器(Selector)
Selector可以称做为选择器,也可以把它叫做多路复用器,可以在单线程的情况下可以去维护多个Channel,也可以去维护多个连接;
核心思想: io多路复用

nio核心设计思想

非阻塞式
选择器 io多路复用原则
缓存区:提高读写效率

2.Redis是如何单线程能够非常好支持并发

首先Redis官方是没有windows版本的,只有redis版本

Redis的底层采用Nio中的多路IO复用的机制,能够非常好的支持这样的并发,从而保证线程安全问题;
但是Nio在不同的操作系统上实现的方式有所不同,在我们windows操作系统使用select实现轮训时间复杂度是为o(n),而且还存在空轮训的情况,效率非常低, 其次是默认对我们轮训的数据有一定限制,所以支持上万的tcp连接是非常难。

所以在linux操作系统采用epoll实现事件驱动回调,不会存在空轮训的情况,只对活跃的 socket连接实现主动回调这样在性能上有大大的提升,所以时间复杂度是为o(1)

注意:windows操作系统是没有epoll,只有linux系统才有epoll

所以为什么nginx、redis都能够非常高支持高并发,最终都是linux中的IO多路复用机制epoll

3.在nio中,服务器和客户端建立通道连接时,服务器端server socket channel、客户端socket channel是两个不同的概念,客户端是多个是吗?

在nio通讯中所有的客户端的数据必须通过channel管道发送到selector选择器统一管理和维护,
服务器端selector选择器管理多个不同的客户端的socketchannel
服务器接受channel就是serversocketchannel。可以这么理解serversocketchannel就是管理所有客户端socketchannel

4. 为什么选择Netty框架 不选择Java原生NIO编程的原因

1.nio原生api非常复杂 学习成本很高
2.netty框架基于nio实现包装

5. Netty优点

API使用简单,开发门槛低;

◎ 功能强大,预置了多种编解码功能,支持多种主流协议;

◎ 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;

◎ 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;

◎ 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;

◎ 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;

◎ 经历了大规模的商业应用考验,质量得到验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。

正是因为这些优点,Netty逐渐成为了Java NIO编程的首选框架。

6. 为什么 netty创建我们服务器端时,要有 boss线程池 和 工作线程池 2个?

在这里插入图片描述
分工明确
boss线程池: 接受 客户端连接 接受请求
工作线程池: 处理客户端的连接 处理我们请求读写操作

7. 客户端 服务端code

public class NettyServer {
    private static int port = 8080;

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
//        NioServerSocketChannel标记当前属于服务器端   NioSocketChannel客户端
        serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ServerHandler());
            }
        });
        try {
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            System.out.println("服务器端启动成功" + port);
//            等待监听我们的请求
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }
}
public class ServerHandler extends SimpleChannelInboundHandler {

//   获取数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {

        // 接受我们的数据
        ByteBuf byteBuf = (ByteBuf) o;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);

        // 响应内容:
        ctx.writeAndFlush(Unpooled.copiedBuffer("平均突破3万月薪", CharsetUtil.UTF_8));
    }
}

短链接 客户端一请求完就关闭

public class socketClinet {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("mayi2".getBytes());
//        while (true) {
//
//        }
            outputStream.close();
            socket.close();
    }
}

while 长链接

public class socketClinet {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("mayi2".getBytes());
        while (true) {

        }
//            outputStream.close();
//            socket.close();
    }
}

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();
        }

    }
}

8.什么 是粘包、拆包

原因的造成:
因为我们现在的tcp连接默认为长连接的形式实现通讯,发送请求之后不会立马关闭连接

客户端与服务器端建立连接,客户端发送一条消息,服务器端马上关闭 客户端与服务器端关闭连接
客户端与服务器端建立连接,客户端发送多条消息,服务器端马上关闭 客户端与服务器端关闭连接

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

9. 为什么会造成 粘包 拆包

tcp 和 缓冲区
tcp为了能够高性能传输,发送和接收都采用缓冲区
1.当我们的客户端发送的数据消息 < 服务器端缓冲区大小 会发生粘包
2.当我们的客户端发送的数据消息 > 服务器端缓冲区大小 会发生拆包
3.接受端不够及时的获取缓冲区的数据,也会产生粘包的问题
4.进行mss(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>mss的时候将发生拆包。

10. 解决粘包 拆包思路:

1.以固定的长度发送数据,到缓冲区
2.可以在数据之间设置一些边界(\n或者\r\n)
3.利用编码器LineBaseDFrameDecoder解决tcp粘包的问题 (自动会识别\n \r)

 ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
 ch.pipeline().addLast(new StringEncoder());
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 LineBasedFrameDecoder(1024));
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ClientHandler());
                    }
                });
        try {
            // 发起同步连接
            ChannelFuture sync = bootstrap.connect().sync();
            sync.channel().closeFuture().sync();
        } catch (Exception e) {

        } finally {
            group.shutdownGracefully();
        }

    }
}
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    // 读取消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println("resp12143243:" + msg.toString(CharsetUtil.UTF_8));
    }

    // 活跃通道可以发送消息
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 发送数据
        for (int i = 0; i < 10; i++) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("mayikt\n", CharsetUtil.UTF_8));
        }
    }
}

public class NettyServer {
    private static int port = 8080;

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
//        NioServerSocketChannel标记当前属于服务器端   NioSocketChannel客户端
        serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                ch.pipeline().addLast(new StringEncoder());
                ch.pipeline().addLast(new ServerHandler());
            }
        });
        try {
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            System.out.println("服务器端启动成功" + port);
//            等待监听我们的请求
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }
}

public class ServerHandler extends SimpleChannelInboundHandler {

    //   获取数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {

        // 接受我们的数据
        ByteBuf byteBuf = (ByteBuf) o;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);
//        String[] split = request.split("\n");
//        for (int i = 0; i <split.length ; i++) {
//            ctx.writeAndFlush(Unpooled.copiedBuffer("平均突破3万月薪", CharsetUtil.UTF_8));
//        }

        // 响应内容:
        ctx.writeAndFlush(Unpooled.copiedBuffer("好好学习\n", CharsetUtil.UTF_8));
    }
}

发布了119 篇原创文章 · 获赞 12 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_44722978/article/details/102786463
今日推荐