Netty 介绍

简介:

  • Netty 是由 JBOSS 提供的一个 java 开源框架。 Netty 提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

  • 也就是说, Netty 是一个基于 NIO 的客户、服务器端编程框架,使用 Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。 Netty 相当于简化和流线化了网络应用的编程开发过程,例如:基于 TCP 和 UDP 的 socket 服务开发。

  • “快速”和“简单”并不用产生维护性或性能上的问题。 Netty 是一个吸收了多种协议(包括 FTP、 SMTP、 HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终, Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。

应用场景:

  • JavaEE: Dubbo
  • 大数据: Apache Storm(Supervisor worker 进程间的通信也是基于 Netty 来实现的)

BIO、 NIO、 AIO 介绍与区别

阻塞与非阻塞
主要指的是访问 IO 的线程是否会阻塞(或者说是等待)
线程访问资源, 该资源是否准备就绪的一种处理方式。

同步和异步

主要是指的数据的请求方式
同步和异步是指访问数据的一种机制

BIO

  • 同步阻塞 IO, Block IO, IO 操作时会阻塞线程, 并发处理能力低。
  • 我们熟知的 Socket 编程就是 BIO,一个 socket 连接一个处理线程(这个线程负责这个 Socket 连接的一系列数据传输操作)。阻塞的原因在于:操作系统允许的线程数量是有限的,多个 socket 申请与服务端建立连接时,服务端不能提供相应数量的处理线程,没有分配到处理线程的连接就会阻塞等待或被拒绝。
  • 每一个请求都需要建立一个线程

NIO

  • 同步非阻塞 IO, None-Block IO
  • NIO 是对 BIO 的改进,基于 Reactor 模型。我们知道,一个 socket 连接只有在特点时候才会发生数据传输 IO 操作,大部分时间这个“数据通道”是空闲的,但还是占用着线程。 NIO 作出的改进就是“一个请求一个线程”,在连接到服务端的众多 socket 中,只有需要进行 IO 操作的才能获取服务端的处理线程进行 IO。这样就不会因为线程不够用而限制了 socket 的接入。
  • 将每个连接注册上selector,单线程处理大多数请求,只需要轮询每个请求

AIO( NIO 2.0)

  • 异步非阻塞 IO
  • 这种 IO 模型是由操作系统先完成了客户端请求处理再通知服务器去启动线程进行处理。 AIO 也称 NIO2.0, 在 JDK7开始支持。

Netty Reactor 模型 - 单线程模型、 多线程模型、 主从多线程模型介绍

单线程模型

  • 用户发起 IO 请求到 Reactor 线程
  • Ractor 线程将用户的 IO 请求放入到通道, 然后再进行后续处理
  • 处理完成后, Reactor 线程重新获得控制权, 继续其他客户端的处理

这种模型一个时间点只有一个任务在执行, 这个任务执行完了, 再去执行下一个任务。

  1. 单线程的 Reactor 模型每一个用户事件都在一个线程中执行:
  2. 性能有极限,不能处理成百上千的事件
  3. 当负荷达到一定程度时,性能将会下降
  4. 某一个事件处理器发生故障,不能继续处理其他事件

Reactor 多线程模型

  • Reactor 多线程模型是由一组 NIO 线程来处理 IO 操作(之前是单个线程),所以在请求处理上会比上一中模型效率更高,可以处理更多的客户端请求。
  • 这种模式使用多个线程执行多个任务, 任务可以同时执行
  • 但是如果并发仍然很大, Reactor 仍然无法处理大量的客户端请求

Reactor 主从多线程模型

  • 这种线程模型是 Netty 推荐使用的线程模型
  • 这种模型适用于高并发场景, 一组线程池接收请求, 一组线程池处理 IO

Netty - 基于 web socket 简单聊天 DEMO 实现

导入依赖

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

编写 Netty Server

package cn.xzzz2020.netty_chat;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class WebSocketNettyServer {
    public static void main(String[] args) {
        //创建两个线程池
        NioEventLoopGroup mainGrp = new NioEventLoopGroup();//主线程池
        NioEventLoopGroup subGrp = new NioEventLoopGroup();//从线程池

        try {
            //创建Netty服务器的启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();

            //初始化启动对象
            bootstrap
                    //指定创建的线程池
                    .group(mainGrp,subGrp)
                    //指定Netty通道类型
                    .channel(NioServerSocketChannel.class)
                    //指定通道初始化器用来加载当channel收到消息后,如何进行业务处理
                    .childHandler(new WebSocketChannelInitializer());
            //绑定服务器端口,以同步的方式启动服务器
            ChannelFuture channelFuture = bootstrap.bind(9090).sync();
            //等待服务器关闭
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //优雅关闭服务器
            mainGrp.shutdownGracefully();
            subGrp.shutdownGracefully();
        }
    }
}

编写通道初始化器

package cn.xzzz2020.netty_chat;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

/** * 通道初始化器 * 用来加载通道处理器 */
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {
    //初始化通道
    //主要在这个方法加载对应的ChannelHandler
    protected void initChannel(SocketChannel ch) throws Exception {
        //获取管道,将一个个的ChannelHandler添加到管道中
        ChannelPipeline pipeline = ch.pipeline();


        //添加一个http的编解码器
        pipeline.addLast(new HttpServerCodec());

        //添加一个用于支持大数据流的支持
        pipeline.addLast(new ChunkedWriteHandler());

        //添加一个聚合器,主要是将httpMessage聚合成FullHttpRequest/Response
        pipeline.addLast(new HttpObjectAggregator(1024*64));


        //需要制定接受请求的路由
        //必须使用一ws结尾的url才能访问
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));

        //添加自定义的Handler
        pipeline.addLast(new ChatHandler());

    }
}

编写处理消息的 ChannelHandler

package cn.xzzz2020.netty_chat;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.text.SimpleDateFormat;
import java.util.Date;

public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    //用来保存所有的客户端连接
    private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);


    private SimpleDateFormat dateFormat  = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //当通道中有新的事件消息,会自动调用
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        //当接收到数据后会自动调用

        //获取客户端发送过来的文本消息
        String text = msg.text();

        System.out.println("接收到的消息数据为:" + text);

        for (Channel client : clients) {
            //将消息发送到所有的客户端
            client.writeAndFlush(new TextWebSocketFrame(dateFormat.format(new Date())+ ":" + text));
        }
    }

    //当有新的客户端连接服务器后,会自动调用这个方法
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //将新的通道加入到clients中
        clients.add(ctx.channel());
    }
}

websocket 以及前端代码编写

WebSocket protocol 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助 HTTP 请求完成。 Websocket 是应用层第七层上的一个应用层协议,它必须依赖 HTTP 协议进行一次握手,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了

前端编写

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>在线聊天室</title>
</head>
<body>
    <input id="message" type="text">
    <input type="button" value="发送消息" onclick="sendMsg()">

    接收到的消息:
    <p style="background-color: #AAAAAA" id="server_message"></p>

    <script>

        var websocket = null;
        //判断当前浏览器是否支持websocket
        if (window.WebSocket) {
            websocket = new WebSocket("ws://127.0.0.1:9090/ws");


            websocket.onopen = function () {

                console.log("建立连接")
            }

            websocket.onclose = function () {
                console.log("断开连接")
            }

            websocket.onmessage = function (e) {
                console.log("接收到服务端的消息:" + e.data);
                var server_message = document.getElementById("server_message");
                server_message.innerHTML +=e.data + "<br/>";
            }
        }
        else {
            alert("当前浏览器不支持web socket")
        }
        
        
        function sendMsg() {

            var message = document.getElementById("message");
            websocket.send(message.value);
        }
    </script>
</body>
</html>

启动测试