【Netty基础一 Echo服务的搭建】

前言

在家闲着没事,刷题也懒得刷,颓废了一周左右发现还是需要开始学习了。
本来想着实习到四月中就溜溜球呢,这下疫情比较严重,可能还得继续呆在公司里了。
这注定是一个十分冷清的春节,十几天来,都是呆在床上度过的。本来很想写写实习的感受,emmm,其实总体感觉很棒,头条的待遇应该来说算是国内前列了。
我所在的组内主要是写Go和Python的,其实我个人是很喜欢JAVA的,这是我比较不喜欢的一点。我还是主要写Go,学了几个公司提供的框架,比如Gin,gorm,kite等等,这些东西其实都是千篇一律,学会了直接就用了,或者没学会看着文档直接用也行。
emmm,觉得学习到的很重要的一点就是技术层面上也算是有一些见识,比如公司的大数据平台,在海量数据里面搜索的解决方案,还有高并发程序,主要都是和大数据相关的问题,AI倒是没有见到,也学习了一些很重要的后端技术,比如中间件Kafka的使用,Redis的使用,MySQL的优化,微服务架构体系等。个人学到的最多还是Redis,很喜欢这个东西。
基本上就是这样,毕竟也才学了不到一个月,希望后边能够学到更多的技术,身边大佬云集倒是真的。废话不多说,开始正题。

Netty

是什么 首先我个人的认识就是一个JAVA实现NIO的框架。(如果不了解什么是BIO/NIO/AIO,可以自行学习)。在高并发的程序里面,NIO是十分必要的。当然现在高版本JDK提供了NIO的实现方式,不采用任何框架,直接调用JDK提供的方法也可以实现NIO,但是这些方法不是很好用,主要是比较复杂,所以大佬们把它封装了一下,开发了一个NIO框架。
用在何处 Netty的使用很广泛,可以说是现在最主流的JAVA NIO框架。比如阿里的Dubbo 里面用到了NIO,还有十分著名的搜索引擎:ES(esticsearch),这是一个做全文搜索的强有力的搜索引擎,常用在日志搜索,全文检索匹配等。ES用Netty做了底层的通信。

Echo 服务

Echo服务是一种类似于心跳检测的服务,用来检测系统是不是在工作,能不能做出相应。

服务端的编写

主函数

package EchoService;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 EchoServer {
   private int port;
   public EchoServer(int port){
       this.port=port;
   }
   public void run() throws InterruptedException {
       // 两个线程组 配置服务端线程组
       EventLoopGroup bossGroup= new NioEventLoopGroup();
       EventLoopGroup workGroup=new NioEventLoopGroup();
       try {
           // 需要一个启动的引导类
           ServerBootstrap serverBootstrap = new ServerBootstrap();
           serverBootstrap.group(bossGroup, workGroup)
                   .channel(NioServerSocketChannel.class)
                   .childHandler(new ChannelInitializer<SocketChannel>() {
                       protected void initChannel(SocketChannel ch) throws Exception {
                           ch.pipeline().addLast(new EchoServerHandler());
                       }
                   });
           System.out.println("Echo 服务启动ing");
           // 绑定端口,同步等待
           ChannelFuture channelFuture = serverBootstrap.bind(this.port).sync();
           // 等待服务端监听端口关闭
           channelFuture.channel().closeFuture().sync();
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           // 释放线程池
           workGroup.shutdownGracefully();
           bossGroup.shutdownGracefully();
       }
   }
   public static void main(String[] args) throws InterruptedException {
       int port=8080;
       if(args.length>0){
           port=Integer.parseInt(args[0]);
       }
       new EchoServer(port).run();
   }
}

这里面的核心就是两个线程组,bossGroup和workGroup。bossGroup该线程组是用来接受TCP连接的,这个线程组里面的线程主要是用来做连接使用,当然有时候也会做一些鉴权。当连接建立好,业务逻辑的处理都是交给workGroup来做。然后是一些启动的配置,新建一个ServerBootstrap类,这个一看名字就知道了,是用来启动服务器的类,然后定义这个类的管道类型,定义为Nio类型,最后一步就是添加Handler,这个Handler就是用来做业务逻辑处理的。接下来是Handler的编写:

处理类

package EchoService;

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

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf data=(ByteBuf)msg;
        System.out.println("当前收到的数据是:"+data.toString(CharsetUtil.UTF_8));
        ctx.writeAndFlush(data);
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("读取成功");

    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 捕获到异常时候的处理,这个时候需要记录日志什么的。
        cause.printStackTrace();
        ctx.close();
    }
}

这个Handler需要继承ChannelInboundHandlerAdapter这个类,看类名大概就知道这是一个输入流处理类,当Server得到输入的时候,把输入流交给这个Handler处理。Handler得到这个输入流,来实现自己的业务逻辑。这里其实我们就没有关心IO是怎么分配的,不用自己完成IO的多路复用,这个Netty帮我们实现了,只需要关心自己的业务逻辑。继承这个类之后,实现里面的一些必要的方法,这个根据自己的需求,比如read方法,这个就是输入的数据,readComplete,这个是读入结束之后该做的事情,还有就是发生异常时应该做的事情。这个函数分类的很详细,耦合性很低,可以自己自由组合,来实现自己的业务。
完成了这些,就可以用了,打开终端,使用telnet来发送包:
在这里插入图片描述
可以看到基本的效果。

客户端的编写

客户端就是建立建立连接,发送数据。其实基本的步骤和服务端差不多,这点和BIO的Socket编程类似。

客户端主函数

package EchoService;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;

public class EchoClient {
    private String host;
    private int port;
    public EchoClient(String host,int port){
        this.host=host;
        this.port=port;
    }
    // 对应的连接逻辑
    public void start(){

        EventLoopGroup group=new NioEventLoopGroup();
        try{
            Bootstrap bootstrap=new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host,port))
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            // 连接到服务端,connect 是异步连接,使用同步等待
            ChannelFuture channelFuture=bootstrap.connect().sync();
            // 阻塞直到客户端通道关闭
            channelFuture.channel().closeFuture().sync();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 优雅的退出
            group.shutdownGracefully();

        }
    }
    public static void main(String[] args) {
        new EchoClient("127.0.0.1",8080).start();
    }
}

客户端的线程组只有一个,这个很容易理解,因为需要建立TCP连接得所有连接被分成了一组。然后就是客户端的启动Bootstrap类,这个需要指定:remoteAddress。然后添加handler。

客户端处理类

package EchoService;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

public class EchoClientHandler extends SimpleChannelInboundHandler {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        ByteBuf data=(ByteBuf)o;
        System.out.println("client recive:"+data.toString());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端启动");
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello,World", CharsetUtil.UTF_8));
    }
    // 监听回调
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("接收成功");
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

这个向服务端发送数据被写在了channelActive函数中,这个函数定义的是当管道建立好连接需要做的事情,显然管道建立好连接时需要做的就是发送数据,所以调用writeAndFlush方法来完成数据的发送,这个方法其实就是把写数据,然后刷新到管道里。
完成以后,先启动服务端,再启动客户端就可以完成通信。

一个使用Netty的简单的Demo就完成了。

发布了371 篇原创文章 · 获赞 50 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章