Netty 解决粘包拆包问题

笔记(讲师的思路)+ 源码(印证讲师的思路)
1、什么是Netty?
基于NIO通信模式封装的一个异步通信框架。

那么我们为什么要讲Netty?Netty在企业应用中用在哪里?
效率非常高(真正的异步通信)
使用更简单(对比我们JDK提供的nio框架)
应用场景:
分布式进程通信
例如: hadoop、dubbo、akka等具有分布式功能的框架,底层RPC通信都是基于netty实现的,这些框架使用的版本通常都还在用netty3.x

2、游戏服务器开发
最新的游戏服务器有部分公司可能已经开始采用netty4.x 或 netty5.x

它相对于我们jdk提供的nio简便在哪里?

以下十个步骤就是开发基于NIO 通信服务端必走的十个步骤:
总结NIO的开发步骤:
1、创建ServerSocketChannel,配置它为非阻塞模式
2、绑定监听,配置Tcp参数,例如backlog大小;
3、创建一个独立的I/O线程,用于轮询多路复用器Selector;
4、创建Selector,将之前创建的ServerSocketChannel注册到Selector上,监听SelectionKey.ACCEPT;
5、启动I/O线程,在循环体中执行Selector.select()方法,轮询就绪的Channel;
6、当轮询到了处于就绪状态的Channel时,需要对其进行判断,如果是OP_ACCEPT状态,说明是新的客户端接入,则调用ServerSocketChannel.accept()方法接受新的客户端;
7、设置新接入的客户端链路SocketChannel为非阻塞模式,配置它的一些TCP参数
8、将SocketChannel注册到Selector,监听OP_READ操作位:
9、如果轮询的Channel为OP_READ,则说明SocketChannel中有新的就绪的数据包需要读取,则构造ByteBuffer对象,读取数据包。
10、如果轮询的Channel为OP_WRITE,说明还有数据没有发送完成,需要继续发送。
一个简单的NIO服务端程序,如果我们直接使用jdk的NIO类库进行开发,需要经过繁琐的事多不操作才完成最基本的消息读取和发送,这就是为什么我们要选择Netty等NIO框架的原因,下面我们看看Netty如何轻松搞定服务器开发的。
这里我们就跳到我们的Netty开发
对比nio 多了些什么东西:
ByteBuf java.nio.Buffer :

Jdk的ByteBuffer 只是提供了基础能力实现

Netty框架它要另起炉灶;ByteBuf flip操作位的操作
使用两个指针来协助缓存区的读写操作。
个人备忘

bio 和 nio 的对比

简介:

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

BIO
同步阻塞式IO,相信每一个学习过操作系统网络编程或者任何语言的网络编程的人都很熟悉,在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。
如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理,大概原理图就像这样:

这里写图片描述

虽然此时服务器具备了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃,NIO可以一定程度解决这个问题。

NIO
同步非阻塞式IO,关键是采用了事件驱动的思想来实现了一个多路转换器。
NIO与BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件,这是怎么做到的呢?
就是多路复用器,可以监听来自多个客户端的IO事件:
A. 若服务端监听到客户端连接请求,便为其建立通信套接字(java中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字。
B. 若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。
C. 监听多个客户端的连接请求和接收数据请求同时还能监听自己时候有数据要发送。

这里写图片描述
总之就是在一个线程中就可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应函数处理。

bio

服务端代码


package com.tony.test.socket;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

public class TonySocketServer {
    @SuppressWarnings("resource")
    public static void main(String[] args) throws IOException {
        // 单一长连接
        // bio
        ServerSocket serverSocket = new ServerSocket(8080);

        while (true) {
            // 获取新连接
            final Socket accept = serverSocket.accept();

            // 开启线程读取客户端请求
            new Thread(new Runnable() {

                public void run() {
                    InputStream inputStream;
                    try {
                        // 不断读请求信息
                        while (true) {
                            inputStream = accept.getInputStream();

                            ByteBuf byteBuf = Unpooled.buffer();
                            byteBuf.writeBytes(inputStream, 9000);
                            // 收到的报文长度
                            int size = byteBuf.readableBytes();

                            if (size == 0) {
                                continue;
                            }
                            byte[] bs = new byte[size];
                            byteBuf.readBytes(bs);

                            System.out.println("上一个数据包余下的长度为 " + size + "合并以后的数据长度为:" + new String(bs));
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }).start();

        }
    }
}

nio

服务端代码

//XNettyServer.java 类

package com.tony.netty.xxx;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class XNettyServer {
    public static void main(String[] args) throws Exception {
        // socket accept 连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // socket read IO处理
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //启动
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //职责链
                            ChannelPipeline p = ch.pipeline();
                            //加入相关业务逻辑处理
                            p.addLast(new XHandller());
                        }

                    });

            // 绑定端口 , 开启服务
            Channel ch = b.bind(8080).sync().channel();
            System.out.println("启动服务 , 绑定端口 :8080");
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

//XHandller.java 类

package com.tony.netty.xxx;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

// 业务处理类
public class XHandller extends ChannelInboundHandlerAdapter {
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    // 读取请求的数据
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf requestBuffer = (ByteBuf) msg;
        // 收到的报文长度
        int size = requestBuffer.readableBytes();
        byte[] bs = new byte[size];
        requestBuffer.readBytes(bs);

        System.out.println("收到数据 :" + new String(bs));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

通过上诉代码 可以看出,bio 和 nio 代码量差不多,显示 nio 看起来更清晰,他的魅力之处可以进行拆包,粘包 ,如下代码展示 netty粘包,拆包代码。


//XNettyServer.java 类

package com.tony.netty.zb;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class XNettyServer {

    public static void main(String[] args) throws Exception {
        // socket accept 连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // socket read IO处理
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //启动
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //职责链  负责调度
                            ChannelPipeline p = ch.pipeline();
                            //加入粘包 拆包处理逻辑 , 加入解码器作用是:保证数据没有问题
                            p.addLast(new XDecoder());

                            //加入相关业务逻辑处理 , 前提是 数据要是正确的才能进行逻辑。
                            p.addLast(new XHandller());
                        }

                    });

            // 绑定端口 , 开启服务
            Channel ch = b.bind(8080).sync().channel();
            System.out.println("启动服务 , 绑定端口 :8080");
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

//XHandller.java 类  

package com.tony.netty.zb;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

// 业务处理类
public class XHandller extends ChannelInboundHandlerAdapter {

    // 计数器
    static int counter = 0;

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf requestBuffer = (ByteBuf) msg;
        // 获取到的数据长度
        int size = requestBuffer.readableBytes();
        byte[] bs = new byte[size];
        requestBuffer.readBytes(bs);
        String request = new String(bs);
        // 对counter 进行+1;
        // i+1  加一次
        // i+1i+1  加两次
        ++counter;
        System.out.println("收到数据" + request+"当前counter的值为 : " + counter);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}




}

//XDecoder.java 类

package com.tony.netty.zb;

import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

// 解码器
public class XDecoder extends ByteToMessageDecoder {

    ByteBuf cache = Unpooled.buffer();

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // in 输入
        // out 输出 我们要输出给后面业务处理的数据

        // i+1
        // i+ 不完整 不做处理 , 缓存起来, 等待下一个数据过来以后在合并
        // i+1i+1混在一起的数据,处理后变成两个[i+1,i+1]
        // i+1i+ 一半的数据

        // 1.合并
        int size = cache.readableBytes();
        ByteBuf message = null;
        if(size > 0) { // 还有数据
            // 合并
            message = Unpooled.buffer();
            message.writeBytes(cache);
            message.writeBytes(in);

            System.out.println("上一个数据包余下的长度为 " + size + "合并以后的数据长度为:" + message.readableBytes());
        } else {
            System.out.println("....");
            message = in;
        }

        // 2.产分报文 , 按照长度拆分 
        // 这个场景下   i+1 是固定的长度 3 个字节
        //
        int s = message.readableBytes();
        int counter = s / 3;
        for (int i = 0; i < counter; i++) {
            // 每次读取三个字节的缓冲数据
            byte[] request = new byte[3];
            message.readBytes(request);
            //  输出给后面业务逻辑去处理
            out.add(Unpooled.copiedBuffer(request));
        }

        // 3.多余的报文缓冲起来
        // i+1i+1i+
        // 
        int ss = message.readableBytes();
        if(ss > 0) {
            System.out.println("多余的数据长度:" + ss);
            cache.clear();
            cache.writeBytes(message.readBytes(ss));
        }

    }
}

各自应用场景

到这里你也许已经发现,一旦有请求到来(不管是几个同时到还是只有一个到),都会调用对应IO处理函数处理,所以:

(1)NIO适合处理连接数目特别多,但是连接比较短(轻操作)的场景,Jetty,Mina,ZooKeeper等都是基于java nio实现。

(2)BIO方式适用于连接数目比较小且固定的场景,这种方式对服务器资源要求比较高,并发局限于应用中。

netty 应用案例

Jetty , Zookeeper , Spark 。

笔记(讲师的思路)+ 源码(印证讲师的思路)

1、什么是Netty?
基于NIO通信模式封装的一个异步通信框架。

那么我们为什么要讲Netty?Netty在企业应用中用在哪里?
效率非常高(真正的异步通信)
使用更简单(对比我们JDK提供的nio框架)
应用场景:
分布式进程通信
例如: hadoop、dubbo、akka等具有分布式功能的框架,底层RPC通信都是基于netty实现的,这些框架使用的版本通常都还在用netty3.x

2、游戏服务器开发
最新的游戏服务器有部分公司可能已经开始采用netty4.x 或 netty5.x

它相对于我们jdk提供的nio简便在哪里?

以下十个步骤就是开发基于NIO 通信服务端必走的十个步骤:
总结NIO的开发步骤:
1、创建ServerSocketChannel,配置它为非阻塞模式
2、绑定监听,配置Tcp参数,例如backlog大小;
3、创建一个独立的I/O线程,用于轮询多路复用器Selector;
4、创建Selector,将之前创建的ServerSocketChannel注册到Selector上,监听SelectionKey.ACCEPT;
5、启动I/O线程,在循环体中执行Selector.select()方法,轮询就绪的Channel;
6、当轮询到了处于就绪状态的Channel时,需要对其进行判断,如果是OP_ACCEPT状态,说明是新的客户端接入,则调用ServerSocketChannel.accept()方法接受新的客户端;
7、设置新接入的客户端链路SocketChannel为非阻塞模式,配置它的一些TCP参数
8、将SocketChannel注册到Selector,监听OP_READ操作位:
9、如果轮询的Channel为OP_READ,则说明SocketChannel中有新的就绪的数据包需要读取,则构造ByteBuffer对象,读取数据包。
10、如果轮询的Channel为OP_WRITE,说明还有数据没有发送完成,需要继续发送。
一个简单的NIO服务端程序,如果我们直接使用jdk的NIO类库进行开发,需要经过繁琐的事多不操作才完成最基本的消息读取和发送,这就是为什么我们要选择Netty等NIO框架的原因,下面我们看看Netty如何轻松搞定服务器开发的。
这里我们就跳到我们的Netty开发
对比nio 多了些什么东西:
ByteBuf java.nio.Buffer :

Jdk的ByteBuffer 只是提供了基础能力实现

Netty框架它要另起炉灶;ByteBuf flip操作位的操作
使用两个指针来协助缓存区的读写操作。
readerIndex writerIndex 开始 是0;
NIO Channel、 它用于非阻塞的I/O操作

Netty Channel Unsafe 内部辅助接口
NioServerSocketChannl
NioSocketChannl

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值