美文网首页IT@程序员猿媛SpringBoot精选
Hello,Netty,不是kitty,喵~!

Hello,Netty,不是kitty,喵~!

作者: 程就人生 | 来源:发表于2019-10-09 20:19 被阅读0次
图片来自网络

说到Netty框架,它可是一款基于Java的高性能NIO通信框架,什么是NIO呢,可以把几个名词对比一下:

BIO(Blocked IO)同步阻塞型IO
AIO(Asynchronous IO) 异步非阻塞IO
NIO(Non-Blocking IO,java中,也称为New I/O),是一种非阻塞的I/O模型;

当然,想要了解这几个名词,还需要了解一下Java的I/O演进之路,它也代表着在Java开发中,通信框架的演进之路,这里就不多说了。

没错,Netty框架采用的便是NIO模型,非阻塞式的通信框架。原来的BIO和AIO模型无论是在性能上,还是在安全上、可靠性上、扩展性上已经无法满足技术场景、市场的需要了,NIO就是在这样的背景下产生的。

HTTP请求是基于C/S模式的,客户端请求一次,服务器响应一次,并且是阻塞式的,每发一次请求需要建立一次连接,而且只能是单向的,要么是客户端请求服务器,要么是服务器返回给客户端,这样一来一回的,对于常规的增删改查、网站展示,也有好处,就是连接在需要的时候建立,不需要的时候直接释放。

NIO模式则是非阻塞的,并且是双工的,在服务器和客户端同时通信,是基于TCP协议的,下面就来一个简单的服务器、客户端的demo,通过服务器和客户端的交互来了解Netty的使用。

首先,需要引入netty的架包,因为是在SpringBoot2.1.4下搭建的客户端和服务端,所以需要引入必要的架包;

<!-- netty架包依赖 -->
        <dependency>
          <groupId>io.netty</groupId>
          <artifactId>netty-all</artifactId>
        </dependency>

第二,服务器端代码;

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

/**
 * Netty 服务端
 * 运行流程:
 * 1.创建一个ServerBootstrap的实例引导和绑定服务器。
 * 2.创建并分配一个NioEventLoopGroup实例以进行事件的处理,比如接受连接以及读写数据。
 * 3.指定服务器绑定的本地的InetSocketAddress。
 * 4.使用一个NettyServerHandler的实例初始化每一个新的Channel。
 * 5.调用ServerBootstrap.bind()方法以绑定服务器。
 *  
 * @author 程就人生
 * @date 2019年10月9日
 */
public class NettyServer {
    
    /**
     * EventLoop接口
     * NioEventLoop中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用NioEventLoop的run方法,执行I/O任务和非I/O任务:
     * I/O任务
     * 即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法触发。
     * 非IO任务
     * 添加到taskQueue中的任务,如register0、bind0等任务,由runAllTasks方法触发。
     * 两种任务的执行时间比由变量ioRatio控制,默认为50,则表示允许非IO任务执行的时间与IO任务的执行时间相等。
     */
    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
    
    private final EventLoopGroup workGroup = new NioEventLoopGroup();
    /**
     * Channel
     * Channel类似Socket,它代表一个实体(如一个硬件设备、一个网络套接字)的开放连接,如读写操作。通俗地讲,Channel字面意思就是通道,每一个客户端与服务端之间进行通信的一个双向通道。
     * Channel主要工作:
     * 1.当前网络连接的通道的状态(例如是否打开?是否已连接?)
     * 2.网络连接的配置参数 (例如接收缓冲区大小)
     * 3.提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。
     *  调用立即返回一个 ChannelFuture 实例,通过注册监听器到ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。
     * 4.支持关联 I/O 操作与对应的处理程序。
     * 不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,下面是一些常用的 Channel 类型
     * NioSocketChannel,异步的客户端 TCP Socket 连接
     * NioServerSocketChannel,异步的服务器端 TCP Socket 连接
     * NioDatagramChannel,异步的 UDP 连接
     * NioSctpChannel,异步的客户端 Sctp 连接
     * NioSctpServerChannel,异步的 Sctp 服务器端连接
     * 这些通道涵盖了 UDP 和 TCP网络 IO以及文件 IO.
     */
    private Channel channel;
    
    /**
     * 启动服务
     * @param port
     */
    public void start(int port){
        
        /**
         * Future
         * Future提供了另外一种在操作完成是通知应用程序的方式。这个对象可以看作一个异步操作的结果占位符。
         * 通俗地讲,它相当于一位指挥官,发送了一个请求建立完连接,通信完毕了,你通知一声它回来关闭各项IO通道,整个过程,它是不阻塞的,异步的。
         * 在Netty中所有的IO操作都是异步的,不能立刻得知消息是否被正确处理,但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,
         * 他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。
         */
        try {
            /**
             * Bootstrap
             * Bootstrap是引导的意思,一个Netty应用通常由一个Bootstrap开始,
             * 主要作用是配置整个Netty程序,串联各个组件,
             * Netty中Bootstrap类是客户端程序的启动引导类,
             * ServerBootstrap是服务端启动引导类。
             */
            ServerBootstrap server = new ServerBootstrap().group(bossGroup,workGroup)
                                    //非阻塞
                                    .channel(NioServerSocketChannel.class)
                                    .localAddress(port)
                                    .childHandler(new ServerChannelInitializer());
            channel = server.bind(port).sync().channel();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 停止服务
     */
    public void destroy(){
        if(channel != null) { 
            channel.close();
        }
        bossGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

    public static void main(String[] args) {
       //制定端口号,然后启动
        NettyServer server = new NettyServer();
        server.start(7788);
    }
}

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * ChannelHanndler
 * ChannelHandler是一个接口,处理I/O事件或拦截I/O操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序。
 * ChannelHandler本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类:
 * ChannelInboundHandler用于处理入站I/O事件
 * ChannelOutboundHandler用于处理出站I/O操作
 * 或者使用以下适配器类:
 * ChannelInboundHandlerAdapter用于处理入站I/O事件
 * ChannelOutboundHandlerAdapter用于处理出站I/O操作
 * ChannelDuplexHandler用于处理入站和出站事件
 * 
 * 处理入站I/O事件
 * @author 程就人生
 * @date 2019年10月9日
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    private static Logger log = LoggerFactory.getLogger(NettyServerHandler.class);
    
    /**
     * 在线人数的集合
     */
    //private static final Map<String, NioSocketChannel> channelMap = new ConcurrentHashMap<>(16);

    //用于记录和管理所有客户端的channle
    private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        
    /**
     * 对每一个传入的消息都要调用;
     * 
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
        log.info(message.toString());
        ctx.channel().writeAndFlush("IP地址为:" + ctx.channel().remoteAddress() + "的用户您好,已收到您发的消息为:" + message);
        //将收到的消息群发
        for(Channel channel : clients){
            channel.writeAndFlush(message.toString());
        }
    }
    
    /**
     * 出现异常时
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        log.info("IP地址为:" + ctx.channel().remoteAddress() + "的用户出现连接异常!");
        ctx.close();
    }
}

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
 * 
 * @author 程就人生
 * @date 2019年10月9日
 */
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        
        ChannelPipeline pipeline = socketChannel.pipeline();      

        // 字符串解码 和 编码
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());

        // 自己的逻辑Handler
        pipeline.addLast("handler", new NettyServerHandler());
    }
}

第三,客户端代码;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
 * 客户端启动类
 * @author 程就人生
 * @date 2019年10月2日
 */
public class HelloWorldClient {
    private  int port;
    private  String address;

    public HelloWorldClient(int port,String address) {
        this.port = port;
        this.address = address;
    }

    public void start(){
        EventLoopGroup group = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                //保证线程安全
                .handler(new ClientChannelInitializer());

        try {
            Channel channel = bootstrap.connect(address,port).sync().channel();
            
//            ChannelFuture future = bootstrap.connect(address,port).sync();
//            future.channel().writeAndFlush("Hello Netty Server ,I am a common client");
//            future.channel().closeFuture().sync();
            //从控制台接收信息
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            for(;;){
                String msg = reader.readLine();
                if(msg == null){
                    continue;
                }             
                channel.writeAndFlush(msg + "\r\n");
            }         
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }

    }

    public static void main(String[] args) {
        //连接指定服务器启动,服务器必须先于客户端启动
        HelloWorldClient client = new HelloWorldClient(7788,"127.0.0.1");
        client.start();
    }
}


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

/**
 * netty客户端各种协议的支持和信息发送,类似于过滤器filter
 * @author 程就人生
 * @date 2019年10月2日
 */
public class HelloWorldClientHandler extends ChannelInboundHandlerAdapter {
    
    /**
     * 发送的消息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("handler 接收到服务器端反馈消息 : "+msg.toString());
    }

    /**
     * 通道的状态,是活动的
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端通道正常连接中...");
    }

    /**
     * 通道的状态,不处于活动状态
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端通道已关闭!");
    }
}

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * 如何绑定handler
 * @author 程就人生
 * @date 2019年10月2日
 */
public class ClientChannelInitializer extends  ChannelInitializer<SocketChannel> {

    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        /**
         * 这个地方的 必须和服务端对应上
         * */
//        ByteBuf delimiter = Unpooled.copiedBuffer("\t".getBytes());
//        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(2048,delimiter)); 
        
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());

        // 绑定自定义的handler,可以绑定多个
        pipeline.addLast("handler", new HelloWorldClientHandler());
        
    }
}

最后,先运行服务器端NettyServer文件,在运行客户端的HelloWorldClient文件,在客户端的控制台输入字符串,然后按下回车,便可以从服务器端的控制台看到输出。

服务端启动信息
客户端启动测试信息

总结
这里只是初次探索,如果想做一个聊天的服务端或客户端,这个demo远远不够,比如心跳如何控制,是在哪一端控制?如何记录在线人员,如何实现一对一,一对多聊天等等,需要扩展的内容还是很多的。

相关文章

  • Hello,Netty,不是kitty,喵~!

    说到Netty框架,它可是一款基于Java的高性能NIO通信框架,什么是NIO呢,可以把几个名词对比一下: BIO...

  • Hello Kitty

    和宝贝合作,在这闷热的天气里画一只粉嫩的kitty猫,心情好好。 发图才发现鼻子歪了,好吧,好吧,歪好,歪好。

  • hello kitty

    hello nmb de kitty

  • Hello Kitty

  • Hello kitty

  • Hello,Kitty

    你喜欢 Hello Kitty吗 ? 这真是个硬核爱好。 我常披着 Kitty的睡衣下楼觅食,粉色、毛绒质感,行走...

  • Hello Kitty

    再好的未来都不如现在,因为任何一种现在都当过未来。

  • 一月读书总结

    1月1日:《Hello Kitty的秘密》★★★★ Hello Kitty的秘密其实就是生逢其时。详情可见《Hel...

  • 无标题文章

    Hello Kitty House 本期《吃光全宇宙》中陈瑶带着王青去的曼谷的Hello Kitty House餐...

  • 170331

    Hello kitty 我对不起你

网友评论

    本文标题:Hello,Netty,不是kitty,喵~!

    本文链接:https://www.haomeiwen.com/subject/vrjipctx.html