Netty框架基础

 

不选择Java原生NIO编程的原因

 

1.NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。

 

2.需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序。

 

3.可靠性能力补齐,工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大。

 

  1. JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有被根本解决。该BUG以及与该BUG相关的问题单可以参见以下链接内容。

 

由于上述原因,在大多数场景下,不建议大家直接使用JDK的NIO类库,除非你精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。

为什么选择Netty框架

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

 

 

基于Netty框架构建Nio编程

 

创建一个Netty项目

Maven依赖

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

 

 

服务器端

public class NettyServer {

   
/**
     * netty
启动端口号
    
*/
   
private static int port = 8080;

   
public static void main(String[] args) {
       
/**
         * 
客户端创建两个线程池组分别为 boss线程组和工作线程组
        
*/
        //
用于接受客户端连接的请求 (并没有处理请求)
       
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
       
// 用于处理客户端连接的读写操作
       
NioEventLoopGroup workGroup = new NioEventLoopGroup();
       
// 用于创建我们的ServerBootstrap
       
ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.
class)
                .childHandler(
new ChannelInitializer<SocketChannel>() {
                   
@Override
                   
protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(
new ServerHandler());
                    }
                });
       
//  绑定我们的端口号码
       
try {
           
// 绑定端口号,同步等待成功
           
ChannelFuture future = serverBootstrap.bind(port).sync();
            System.
out.println("服务器启动成功:" + port);
           
// 等待服务器监听端口
           
future.channel().closeFuture().sync();
        }
catch (Exception 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("你好啊", CharsetUtil.UTF_8));


    }


}

 

 

 

 

客户端

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();
       
for (int i = 0; i < 10; i++) {
            outputStream.write(
"lgcc".getBytes());
        }
        outputStream.close();
        socket.close();
    }
}

 

 

Netty客户端

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

    }
}

 

 

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 {
        System.
out.println("resp" + msg.toString(CharsetUtil.UTF_8));
    }
}

 

粘包拆包解决方案:

 

什么 是粘包、拆包

 

原因的造成:

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

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

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

 

什么是粘包:多次发送的消息,客户端一次合并读取 msg+msg

什么是拆包:第一次完整消息+第二次部分消息组合 、第二次缺失的消息

Msg Msg=msgmsg

Msg Msg=MsgM  sg

 

 

粘包与拆包产生的背景:

Tcp协议为了高性能的传输,发送和接受的时候都采用了缓冲区

  1. 当我们的应用程序发送的数据大于套字节缓冲区的时候,就会实现拆包。
  2. 当我们的应用程序写入的数据小于套字节缓冲区的时候,多次发送的消息会合并到一起接受,这个过程我们可以称做为粘包。
  3. 接受端不够及时的获取缓冲区的数据,也会产生粘包的问题
  4. 进行mss(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>mss的时候将发生拆包。

 

 

解决思路:

1.以固定的长度发送数据,到缓冲区

2.可以在数据之间设置一些边界(\n或者\r\n)

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

serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
        .childHandler(
new ChannelInitializer<SocketChannel>() {
           
@Override
           
protected void initChannel(SocketChannel socketChannel) throws Exception {
               
// 设置我们分割最大长度为1024
               
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
               
// 获取数据的结果为string类型
               
socketChannel.pipeline().addLast(new StringEncoder());
                socketChannel.pipeline().addLast(
new ServerHandler());
            }
        });

 

 

总结 

为什么放弃nio采用netty实现?

  1. Nio原生的api非常复杂、学习成本非常高
  2. Netty框架对NIO实现了包装

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

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

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

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

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

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

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

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

 

什么是粘包、拆包

       因为我们现在tcp协议默认是长连接形式实现通讯,发送请求完了之后整个连接暂时不会关闭

    1.客户端与服务器端建立连接的时候,客户端发送一条消息,客户端与服务器连接关闭

     2.长连接,客户端与服务器端建立连接的时候,客户端发送多条消息,客户端与服务器连接关闭

   Msg Msg

    什么是粘包:多次发送的消息,服务器一次合并读取msgmsg

   Msgm sg   

    什么是拆包:多次发送消息 服务器读取第一条数据完整+第二条不完整数据  第二条不完整数据 Msgm sg

   为什么会造成拆包和粘包 前提长连接(时刻在发送)、其次缓冲区(缓冲器大小时固定的,一旦达到最大值就容易引起拆包)

原因的造成:

     Tcp协议为了能够高性能的传输数据,发送和接受时候都会采用缓冲区,必须等待缓冲区满了以后才可以发送或者读取;

  1. 当我们的应用程序如果发送的数据大于了我们的套字节的缓冲区大小的话,就会造成了拆包。

     拆分成多条消息读取

  1. 当我们应用程序如果发送的写入的消息如果小于套字节缓冲区大小的时候

如何解决粘包和拆包:

  1. 以特定标记/n  /t分割
  2. 可以采用api

为什么要使用nio(再复习下nio的优缺点.)

     优点:   

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

    选择器Selector:能够帮助我们实现io多路复用原则、非阻塞式

    缓冲区buffer:    提高读写操作

   nio也有缺点:那就是select选择器在windows系统上运行,底层实现轮训机制,遍历所有io通道,然后进行复用.所以性能上有所缺陷,时间复杂度是为 o(n),但是在linux上不存在这种问题,因为linux自带epooll实现事件驱动回调形式通知,不存在轮训的问题.时间复杂度为是o(1)所以nio程序在linux上运行效率比windows高.   下面图片有解释.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值