netty服务的搭建

启动springboot的主类

@SpringBootApplication

@Configuration

@EnableAutoConfiguration

public class MainApplication {

    public static void main(String[] args) {

        SpringApplication.run(MainApplication.class, args);

    }

}

 

通过@Configuration注解在springboot服务启动时注入,

@Bean(initMethod = "start", destroyMethod = "stop")

    @ConditionalOnMissingBean

    @ConditionalOnProperty(prefix = "netty.server",value = "enabled",havingValue = "true")

    NettyServer nettyServer (){

        return  new NettyServer( properties);

    }

这段代码中initMethod代表此bean对象初始化创建时调用该对象的start方法destroyMethod= "stop"表示该bean对象销毁时调用该beanstop对象@ConditionalOnMissingBean注解表示如果在spring容器中有该bean对象则不创建,如果没有该对象则初始化创建一个新的该bean对象 ConditionalOnProperty(prefix = "netty.server",value = "enabled",havingValue = "true")表示从resource文件夹下面的application.property文件中读取属性名以netty.server开头的属性@Value注解表示从application.property文件中读取向对应的属性。所以以后项目中就可以把一下在项目中容易修改的属性值放在application.property文件中。这样即使项目打包了也可以修改application.property文件中的属性来配置项目。简单灵活。

 

 

 

@Configuration

public class WebSocketConfiguration {

    @Autowired

    private NettyServerProperties properties;

   

    @Value("${httpclient.connecttimeout:1500}")

    private int connectTimeout;

   

    @Value("${httpclient.requesttimeout:1500}")

    private int readTimeout;

   

    @Value("${httpclient.requesttimeout:3000}")

    private int requestTimeout;

 

    @Bean(initMethod = "start", destroyMethod = "stop")

    @ConditionalOnMissingBean

    @ConditionalOnProperty(prefix = "netty.server",value = "enabled",havingValue = "true")

    NettyServer nettyServer (){

        return  new NettyServer( properties);

   

    /**

     * 后续的HTTP 访问完全依靠这个类生成 AsyncHttpClient 对象

     *

     * @param factory

     * @return

     */

    @Bean(destroyMethod="close")

    public AsyncHttpClient asyncHttpClient(AsyncHttpClientConfig config) {

       return new DefaultAsyncHttpClient(config);

    }

 

    @Bean

    public AsyncHttpClientConfig clientConfig() {

       AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()

              .setWebSocketMaxFrameSize(properties.getMaxSize())

              .setConnectTimeout(connectTimeout)

              .setReadTimeout(readTimeout)

              .setRequestTimeout(requestTimeout)

              .build();

       return config;

    }

}

在前面当NettyServer的对象bean初始化时启动start方法,start利用java8lambda表达式来开启一个线程去调用startServer方法开启netty服务。

其中的ApplicationContextAware接口用来  为了让Bean获取它所在的Spring容器,可以让该Bean实现ApplicationContextAware接口 为了让Bean获取它所在的Spring容器,可以让该Bean实现ApplicationContextAware接口

public class NettyServer implements ApplicationContextAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class);

    private NettyServerProperties properties;

    private Channel serverChannel;

    private ApplicationContext applicationContext;

   

    @Override

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

       this.applicationContext = applicationContext;

    }

   

    public NettyServer(NettyServerProperties properties) {

       this.properties = properties;

    }

    public void start() {

       //需要开启一个新的线程来执行netty server 服务器

       Thread t = new Thread(()->startServer());

       //t.setDaemon(true);

       t.setName("netty thread");

       t.start();

    }

    public void stop() {

       if(serverChannel != null) {

           try {

              serverChannel.close().get(2L, TimeUnit.SECONDS);

           } catch (InterruptedException | ExecutionException |TimeoutException e) {

           }

           serverChannel = null;

       }

    }

    此方法用来配置开启netty服务其中EventLoopGroup表示netty的线程池,bootstrap.group(boss,work)表示绑定线程池channel表示// 指定使用的channel

.localAddress( port )// 绑定监听端口  .childHandler(new
ChannelInitializer<SocketChannel>() { // 用来绑定客户端连接时候触发操作

 

 

ChannelFuture cf =
sb.bind().sync();
// 服务器异步创建绑定  cf.channel().closeFuture().sync();
// 关闭服务器通道

 

 

private void startServer() {

       //服务端需要2个线程组  boss线程组处理客户端连接  work线程组进行客服端连接之后的处理

        EventLoopGroup boss = new NioEventLoopGroup();

        EventLoopGroup work = new NioEventLoopGroup();

        try {

  ServerBootstrap bootstrap = new ServerBootstrap();

            //服务器 配置   

//绑定线程池                 //指定使用的channel  bootstrap.group(boss,work).channel(NioServerSocketChannel.class) .childHandler(newChannelInitializer<SocketChannel>() { //初始化ChannelIntialize

                protected void initChannel(SocketChannel socketChannel) throws Exception {

                    // HttpServerCodec:将请求和应答消息解码为HTTP消息

                    socketChannel.pipeline().addLast("http-codec",new HttpServerCodec());

                    // HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息

                    socketChannel.pipeline().addLast("aggregator",new HttpObjectAggregator(properties.getMaxSize()));

                    // ChunkedWriteHandler:向客户端发送HTML5文件

                    socketChannel.pipeline().addLast("http-chunked",new ChunkedWriteHandler());

                    // 进行设置心跳检测

                    socketChannel.pipeline().addLast(new IdleStateHandler(properties.getIdleTime(),

                        properties.getIdleTime(), properties.getIdleTime()*10, TimeUnit.SECONDS));

                    // 配置通道处理  来进行业务处理

//指定客户端触发操作 WebSocketServerHandler                     socketChannel.pipeline().addLast(applicationContext.getBean(WebSocketServerHandler.class));

                }

            }).option(ChannelOption.SO_BACKLOG,1024).childOption(ChannelOption.SO_KEEPALIVE,true);

            //绑定端口  开启事件驱动

            LOGGER.info("【服务器启动成功========端口:" + properties.getPort() + "");

            serverChannel = bootstrap.bind(properties.getPort()).sync().channel();

            serverChannel.closeFuture().sync();

        }catch (Exception e){

            e.printStackTrace();

        }finally {

            //关闭资源

            boss.shutdownGracefully();

            work.shutdownGracefully();

        }

    }

 

 

}

 

 

 

 

SimpleChannelInboundHandler

 

 

 

@Component

@Scope("prototype") // 必须是prototype!!!

public class WebSocketServerHandler extends
SimpleChannelInboundHandler<Object> implements ApplicationContextAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServerHandler.class);

    private ApplicationContext applicationContext;

    private Map<ChannelId, WebSocketSessionHandler> handlers = new
ConcurrentHashMap<>();

    @Autowired

    NettyServerProperties properties;

    @Override

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

       this.applicationContext = applicationContext;

    }

    @Override       //业务的入口  ctx是channel的关键用来将channel关键起来

    public void channelRead0(ChannelHandlerContext ctx, Object msg) {

        if (msg instanceof FullHttpRequest) {

            handleHttpRequest(ctx, (FullHttpRequest)
msg);

        } else if (msg instanceof WebSocketFrame) {

            handleWebSocketFrame(ctx, (WebSocketFrame) msg);

        }

    }

    @Override

    public void channelReadComplete(ChannelHandlerContext ctx) {

        ctx.flush();

    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {

        // Handle a bad request.

    boolean iswebSocket = "websocket".equals(req.headers().get("Upgrade"));

        if (!req.decoderResult().isSuccess() || !iswebSocket) {

            sendHttpResponse(ctx, req, new
DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));

            return;

        }

        String urireq.uri();

        if(iswebSocket) {

         WebSocketServerHandshakerFactory wsFactory = new
WebSocketServerHandshakerFactory(uri, null, true, properties.getMaxSize());

         WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);

         if (handshaker == null) {

WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());

             } else {

                 ChannelFuture channelFuture = handshaker.handshake(ctx.channel(), req);

                 channelFuture.addListener(f->{

                // 握手成功之后,业务逻辑

                     if(f.isSuccess()) {

                     newWebSocketSession(ctx, req, handshaker);

                 } else {

                     sendHttpResponse(ctx, req, new
DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));

                 }

                 });

                 try {

                  channelFuture.await(0);

              } catch (InterruptedException e) {

              }

             }

        } else {

        processHttp(ctx, req);

        }

 

 

    }

 

 

 

 

 

 

 

 

 

    private void newWebSocketSession(ChannelHandlerContext ctx,  FullHttpRequest req,

 

 

 

 

           WebSocketServerHandshaker handshaker) {

          URI uri = null

try {

  uri = new URI(req.uri());

   } catch (URISyntaxException e) {

  LOGGER.error(e.getMessage(), e);

return;

 }

//这里的代码比较丑陋,先这样处

 //后续看有没有优雅一些的方

 WebSocketSessionHandler handler;

if(uri.getPath().startsWith("/dds/v1")) {

handler = applicationContext.getBean(DDSSessionHandler.class, ctx, handshaker);

 } else if(uri.getPath().startsWith("/asr/v1")) {

handler = applicationContext.getBean(DummyAsrSessionHandler.class, ctx, handshaker);

} else {

handler = applicationContext.getBean(DefaultSessionHandler.class, ctx, handshaker);

 }

handlers.put(ctx.channel().id(), handler);

  }

 private void processHttp(ChannelHandlerContext ctx, FullHttpRequest req) {

 sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND));

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {

       if (frame instanceof CloseWebSocketFrame) {

        WebSocketSessionHandler
handler = handlers.get(ctx.channel().id());

        if(handler != null) {

            handler.closeSession((CloseWebSocketFrame)frame);

        }

            return;

        }

        if (frame instanceof PingWebSocketFrame) {

            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));

        } else {

        WebSocketSessionHandler
handler = handlers.get(ctx.channel().id());

        if(handler != null) {

            handler.processFrame(frame);

        }
else {

            ctx.channel().writeAndFlush(new
TextWebSocketFrame("{}"));

        }

        }

    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {

        if (res.status().code() != 200) {

            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(),
CharsetUtil.
UTF_8);

            res.content().writeBytes(buf);

            buf.release();

            HttpUtil.setContentLength(res, res.content().readableBytes());

        }

        ChannelFuture f = ctx.channel().writeAndFlush(res);

        if (!HttpUtil.isKeepAlive(req) || res.status().code() !=
200) {

            f.addListener(ChannelFutureListener.CLOSE);

        }

    }

 

 

    @Override

 

 

 

 

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {

 

 

 

 

        cause.printStackTrace();

 

 

 

 

        ctx.close();

 

 

 

 

    }

 

 

 

 

 

 

 

 

 

    @Override

 

 

 

 

    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

 

 

 

 

    WebSocketSessionHandler
handler = handlers.remove(ctx.channel().id());

    if(handler != null) {

        handler.clear();

    }

  }

}

 Netty 服务器的通信步骤为:
     1.创建两个NIO线程组,一个专门用于接收来自客户端的连接,另一个则用于处理已经被接收的连接。
     2.创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等。
     3.创建一个用于实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置接受传出数据的字符集、格式以及实际处理数据的接口。
     4.绑定端口,执行同步阻塞方法等待服务器端启动即可。
     
     在Netty里,Channel是通讯的载体,而ChannelHandler负责Channel中的逻辑处理。一个Channel包含一个ChannelPipeline,所有ChannelHandler都会注册到ChannelPipeline中,并按顺序组织起来
     每个ChannelHandler 被添加到ChannelPipeline 后,都会创建一个ChannelHandlerContext 并与之创建的ChannelHandler 关联绑定
     ChannelHandler通过ChannelHandlerContext来操作channel和channelpipeline
     1.Channel:
     1 工作原理
     Channel是Netty的核心概念之一,它是Netty网络通信的主体,由它负责同对端进行网络通信、注册和数据操作等功能。
     一旦用户端连接成功,将新建一个channel同该用户端进行绑定,channel从EventLoopGroup获得一个EventLoop,并注册到该EventLoop,channel生命周期内都和该EventLoop在一起(注册时获得selectionKey)
     channel同用户端进行网络连接、关闭和读写,生成相对应的event(改变selectinKey信息),触发eventloop调度线程进行执行,如果是读事件,执行线程调度pipeline来处理用户业务逻辑。
     2线程
     多个channel可以注册到一个eventloop上,所有的操作都是顺序执行的,eventloop会依据channel的事件调用channel的方法进行相关操作,每个channel的操作和处理在eventloop中都是顺序的。
     2. ChannelPipeline和ChannelHandler
     ChannelPipeline和ChannelHandler用于channel事件的拦截和处理,Netty使用类似责任链的模式来设计ChannelPipeline和ChannelHandler
     ChannelPipeline相当于ChannelHandler的容器,channel事件消息在ChannelPipeline中流动和传播,相应的事件能够被ChannelHandler拦截处理、传递、忽略或者终止。
     基于ChannelHandler开发业务逻辑,基本不需要关心网络通讯方面的事情,专注于编码/解码/逻辑处理就可以了。Handler也是比较方便的开发模式,在很多框架中都有用到。
     Channel启动初始化的时候,默认是DefaultChannelPipeline,Channel总是对应一个pipeline
     在讲Inbound事件传播机制的时候,说过每次传播会遍历Pipeline中的Handler然后找到Inbound类型进行调用
     2.1 INBOUD和OUTBOUND事件
    inbound:当发生某个I/O操作时由IO线程流向用户业务处理线程的事件,如链路建立、链路关闭或者读完成等
    outbound:由用户线程或者代码发起的IO操作事件
    2.2 ChannelHandlerContext
    每个ChannelHandler 被添加到ChannelPipeline 后,都会创建一个ChannelHandlerContext 并与之创建的ChannelHandler 关联绑定
    ChannelHandler通过ChannelHandlerContext来操作channel和channelpipeline
    ChannelHandler负责I/O事件或者I/O操作进行拦截和处理,用户可以通过ChannelHandlerAdapter来选择性的实现自己感兴趣的事件拦截和处理。
    由于Channel只负责实际的I/O操作,因此数据的编解码和实际处理都需要通过ChannelHandler进行处理。
    ChannelPipeline是线程安全的,多个业务线程可以并发的操作ChannelPipeline;ChannelHandler不是线程安全的,用户需要自己保重ChannelHandler的线程安全
    ChannelHandlerContext保存了Netty与Handler相关的的上下文信息。而咱们这里的DefaultChannelHandlerContext,则是对ChannelHandler的一个包装。
    一个DefaultChannelHandlerContext内部,除了包含一个ChannelHandler,还保存了"next"和"prev"两个指针,从而形成一个双向链表。
    3. ChannelFuture与ChannelPromise
   在Netty中,所有的I/O操作都是异步的,因此调用一个I/O操作后,将继续当前线程的执行,但I/O操作的结果怎么获得?——ChannelFuture。
   回调方法通过监听的形式实现:ChannelFutureListener。
   Channel是连接的通道,是ChannelEvent的产生者,而ChannelPipeline可以理解为ChannelHandler的集合。
   一个Channel在创建的时候就会创建一个对应的ChannelPipeline,
通过上面的分析,可以看到ChannelPipeline的设计是线程安全的,有很多地方的操作就是为了这个线程安全做了很多的操作,例如addLast调用handlerAdded会转换成EventLoop队列任务,Netty中很多地方都是类似的,为了避免这种多线程操作的问题都是先转成队列的任务,从而转换成单线程的操作,这种设计需要好好琢磨
 Netty中随着一个Channel的创建,会连带创建一个ChannelPipeline,这个ChannelPipeline就像一个处理各种事件的管道,负责去处理Channel上发生的事件,例如连接事件,读事件,写事件等。
更深入的说,处理的并不是ChannelPipeline,而是ChannelPipeline中一个个的ChannelHandler,其结构如下
  ChannelPipeline中有很多Handler(其实是Context类型,Context封装了Handler),组成了一个双向的链表,同时初始化的时候就会带有一个头结点和尾结点,自定义的ChannelHandler都会添加到Head和Tail之间。
同时Netty定义了两种事件:

inbound:事件从Head往Tail方向传递,实现ChannelInboundHandler的ChannelHandler为处理inbound事件的ChannelHandler
outbound:事件从Tail往Head方向传递,实现ChannelOutboundHandler的ChannelHandler为处理inbound事件的ChannelHandlerContext和Pipeline、Handler、Channel、EventLoop是一对一的关系。
将一个自定义的ChannelHandler加入到了ChannelPipeline中
直接使用Channel.writeAndFlush会从Tail开始传播,而使用ctx.writeAndFlush则是从当前Handler开始往前传播

ChannelPipeline的设计是线程安全的,有很多地方的操作就是为了这个线程安全做了很多的操作,例如addLast调用handlerAdded会转换成EventLoop队列任务,Netty中很多地方都是类似的,为了避免这种多线程操作的问题都是先转成队列的任

在Netty 中, 一个 NioEventLoop 通常需要肩负起两种任务, 第一个是作为 IO 线程, 处理 IO 操作; 第二个就是作为任务线程, 处理 taskQueue 中的任务.

netty的程序的启动(在服务端一般是两个NioEventLoopGroup线程池,一个boss, 一个worker; 对于客户端一般是一个线程池)。我们一般是使用NIO,所以本文也全部是基于NIO来分析的,当然netty也支持BIO。

netty之所以是高性能NIO框架,其中主要贡献之一就是netty的线程模型的高性能,我们都知道netty的线程模型是基于Reactor线程模型

Reactor线程模型

Reactor线程模型有三种

  • 单线程模型
  • 多线程模型
  • 主从Reactor线程模型

    NIOEventLoopGroup就是一个线程池实现,通过设置不同的NIOEventLoopGroup方式就可以对应三种不同的Reactor线程模型。

    这里我只给出服务端的配置,对于客户端都是一样的。单线程模型:

  •  

 NIO中则直接将buffer这个概念封装成了对象,其中最常用的大概是ByteBuffer了。于是使用方式变为了:将数据写入Buffer,flip()一下,然后将数据读出来。于是,buffer的概念更加深入人心了!

在Netty里,Channel是通讯的载体,而ChannelHandler负责Channel中的逻辑处理。一个Channel包含一个ChannelPipeline,所有ChannelHandler都会注册到ChannelPipeline中,并按顺序组织起来。在Netty中,ChannelEvent是数据或者状态的载体,例如传输的数据对应MessageEvent,状态的改变对应ChannelStateEvent。当对Channel进行操作时,会产生一个ChannelEvent,并发送到ChannelPipeline。ChannelPipeline会选择一个ChannelHandler进行处理。这个ChannelHandler处理之后,可能会产生新的ChannelEvent,并流转到下一个ChannelHandler。Netty的ChannelPipeline包含两条线路:Upstream和Downstream。Upstream对应上行,接收到的消息、被动的状态改变,都属于Upstream。Downstream则对应下行,发送的消息、主动的状态改变,都属于Downstream。ChannelPipeline接口包含了两个重要的方法:sendUpstream(ChannelEvent e)sendDownstream(ChannelEvent e),就分别对应了Upstream和Downstream。ChannelPipeline的主要的实现代码在DefaultChannelPipeline类里。ChannelHandlerContext保存了Netty与Handler相关的的上下文信息。而咱们这里的DefaultChannelHandlerContext,则是对ChannelHandler的一个包装。一个DefaultChannelHandlerContext内部,除了包含一个ChannelHandler,还保存了"next"和"prev"两个指针,从而形成一个双向链表。ChannelHandler的编程模型,基于ChannelHandler开发业务逻辑,基本不需要关心网络通讯方面的事情,专注于编码/解码/逻辑处理就可以了。Handler也是比较方便的开发模式,在很多框架中都有用到。每一个channel都需要有相应的channelPipeline,当为channel设置了channelPipeline后就不能再为channel重新设置 channelPipeline。应该最好是通过Channels 这个帮助类来生成ChannelPipeline 而不是自己去构建ChannelPipeline 。

ChannelGroup :channel的组集合,他包含一个或多个open的channel,closed channel会自动从group中移除,一个channel可以在一个或者多个channelGroup 如果想将一个消息广播给多个channel,可以利用group来实现 

ChannelFuture :在netty中,所有的io传输都是异步,所有那么在传送的时候需要数据+状态来确定是否全部传送成功,而这个载体就是ChannelFuture

 Channel是连接的通道,是ChannelEvent的产生者,而ChannelPipeline可以理解为ChannelHandler的集合在使用传统的ServerSocket和Socket的时候 很多时候程序是会阻塞的
比如 serversocket.accept() , socket.getInputStream().read() 的时候都会阻塞 accept()方法除非等到客户端socket的连接或者被异常中断 否则会一直等待下去
read()方法也是如此 除非在输入流中有了足够的数据 否则该方法也会一直等待下去知道数据的到来.在ServerSocket与Socket的方式中 服务器端往往要为每一个客户端(socket)分配一个线程,而每一个线程都有可能处于长时间的阻塞状态中.而过多的线程也会影响服务器的性能.在JDK1.4引入了非阻塞的通信方式,这样使得服务器端只需要一个线程就能处理所有客户端socket的请求.
下面是几个需要用到的核心类
ServerSocketChannel: ServerSocket 的替代类, 支持阻塞通信与非阻塞通信.
SocketChannel: Socket 的替代类, 支持阻塞通信与非阻塞通信.
Selector: 为ServerSocketChannel 监控接收客户端连接就绪事件, 为 SocketChannel 监控连接服务器就绪, 读就绪和写就绪事件.
SelectionKey: 代表 ServerSocketChannel 及 SocketChannel 向 Selector 注册事件的句柄. 当一个 SelectionKey 对象位于Selector 对象的 selected-keys 集合中时, 就表示与这个 SelectionKey 对象相关的事件发生了.在SelectionKey 类中有几个静态常量。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值