【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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章