還在爲Java的NIO編程發愁嗎?也許你該接觸下Netty網絡編程了(從Netty環境搭建到Netty入門案例)

Netty是業界最流行的NIO框架之一,它的健壯性、功能、性能和可擴展性在同類框架中都是首屈一指的,它已經得到了成百上千的商用項目的驗證。本文是Netty網絡編程的入門教程,從Netty開發環境的搭建到Netty入門實例編程。

1. Netty開發環境搭建

環境配置:

  • JDK1.8
  • IDEA 2020.1
  • Netty 4.0.56

由於Netty是一個基於Java的NIO編程框架,我們需要實現下載安裝好JDK並且配置環境變量,在這裏我就不介紹JDK的安裝配置了,不懂的大家可以自行百度。關於編程所使用的工具,我使用的是IDEA,大家也可以選擇其它的環境,下面簡要介紹Netty開發環境搭建。

1.1. 下載Netty的jar包

訪問Netty的官網http://netty.io/,從Downloads標籤頁選擇下載安裝,將壓縮包下載完成之後,進行解壓,目錄結構如下圖所示:

在這裏插入圖片描述
在這個裏面我們可以找到各個模塊的jar包和源碼,由於我們直接以二進制類庫的方式使用Netty,所以只需要獲取netty-all-4.0.56.Final.jar即可,該jar包在all-in-one目錄下。

1.2 搭建Netty工程

使用IDEA創建普通的Java工程,創建相應的package和類,如下圖所示:
在這裏插入圖片描述
接下來我們在所建的java工程添加外部jar包,步驟爲:File->Project Structure -> Libraries ->點擊+ ->選擇java ->選擇netty-all-4.0.56.Final.jar添加即可,如下圖所示:
在這裏插入圖片描述

1.3 使用Maven工程

除了使用上述方法直接建立java工程然後導入jar包之外,我們還可以使用Maven工程,添加netty相應的依賴即可,下面給出netty的maven依賴,具體的maven工程構建可以百度。

  <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
    <version>X.Y.Z.Q</version>
    <scope>compile</scope>
  </dependency>

2. Netty編程入門案例

本節我們將使用一個簡單的時間服務器的例子來演示如何使用Netty進行網絡編程。下面分服務端和客戶端分別進行介紹。

2.1 Netty服務端編程

下面首先給出代碼,然後再進行相應的解釋,具體代碼如下所示。

package netty.base;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
 * created by LMR on 2020/5/20
 */
public class TimeServer {
    public static void main(String[] args) throws Exception{
        int port = 8080;
        new TimeServer().bind(port);
    }

    public void bind(int port) throws Exception{
        //配置服務端NIO線程組
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());
            //綁定端口,同步等待成功
            ChannelFuture f = serverBootstrap.bind(port).sync();
            //等待服務端監聽端口關閉
            f.channel().closeFuture().sync();
        }finally {
            //優雅退出,釋放資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }
}

在bind方法中,首先創建了兩個NioEventLoopGroup實例。NioEventLoopGroup本子上是個線程組,專門用於網絡事件的處理,與Java的NIO編程中的Reactor類似。這裏的兩個實例,一個用於服務端接受客戶端連接,另一個用於進行SocketChannel的網絡讀寫。之後創建ServerBootstrao對象,器用於啓動NIO服務端,主要是爲了降低服務端的開發難度。接下來調用ServerBootstrap的group方法,將兩個NIO線程組當作參數傳入。然後設置創建的Channel爲NioServerSocketChannel,並且配置它的TCP參數,最後綁定I/O時間的處理類ChildChannelhandler。

配置完成之後,就會調用ServerBootstrap的bind方法綁定監聽端口,並且調用它的同步阻塞方法sync等待綁定按操作完成。完成之後會返回一個ChannelFuture ,主要用於異步操作的通知回調。接下來看看TimeServerHandler類是如何實現的。

package netty.base;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * created by LMR on 2020/5/20
 */
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        //將消息轉爲buf
        ByteBuf buf=(ByteBuf) msg;
        //創建一個buf長度的數組
        byte [] requestbyte=new byte[buf.readableBytes()];
        //將緩衝區的數據寫入到字節數組
        buf.readBytes(requestbyte);
        //根據字節數組創建字符串
        String request=new String(requestbyte,"utf-8");
        System.out.println("The time server receive order : " + request);
        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(request) ? new
                java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
        //響應給客戶端
        ByteBuf resBuf= Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resBuf)	;
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    //刷新數據,將數據從緩衝區刷新到Channel中去
        ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //當異常發生時,關閉ChannelHandlerContext,釋放和ChannelHandlerContext相關聯的句柄等資源
        ctx.close();
    }
}

TimeServerHandler繼承ChannelInboundHandlerAdapter ,它用於對網絡事件進行讀寫操作,通常我們只需要關注channelRead和exceptionCaught方法。關於這兩個方法中的代碼,下上面已經有了詳細的註釋,在這裏就不再進行介紹。接下來介紹客戶端的實現代碼。

2.2 Netty客戶端編程

Netty客戶端的開發相比於服務端更簡單,下面來看具體的實現代碼。

package netty.base;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
 * created by LMR on 2020/5/20
 */
public class TimeClient {

    public static void main(String[] args) throws Exception{
        int port = 8080;
        new TimeClient().connect("127.0.0.1", port);
    }
    public void connect(String host, int port) throws Exception{
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });
            //發起異步連接操作
            ChannelFuture f = bootstrap.connect(host, port).sync();
            //等待客戶端連接關閉
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }
}

首先在connect方法中創建客戶端處理I/O讀寫的NioEventLoopGroup線程組,然後繼續創建客戶端輔助啓動類Boostrap,隨後對其進行配置 。與服務端不同點在於,客戶端的Channel需要設置爲NioSocketChannel類型,然後爲其添加handler,這裏我們使用了匿名內部類(不懂得自行百度或者留言),實現initChannel方法,其作用是當創建NioSocketChannel成功之後,在初始化它的時候將它的ChannelHandler設置到ChannelPipeline中,用於處理網絡I/O事件。

下面我們看一下TimeClientHandler的代碼實現。

package netty.base;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
 * created by LMR on 2020/5/20
 */
public class TimeClientHandler extends ChannelInboundHandlerAdapter {

    private final ByteBuf message;
   public TimeClientHandler(){
   //創建發送消息的字節數組
       byte[] req = "QUERY TIME ORDER".getBytes();
       //將字節數組寫入緩衝區
       message = Unpooled.buffer(req.length);
       message.writeBytes(req);
   }
   //當客戶端和服務端TCP鏈路建立成功之後會調用此方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    //將消息從緩衝區刷新到Channel
        ctx.writeAndFlush(message);
    }
        //當客戶端接受到服務端傳來的消息時執行
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

       //處理服務端發送過來的消息
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("Now is : " + body);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
       ctx.close();
    }
}

在客戶端處理類中需要注意的就是上述代碼中重寫的三個方法,關於方法的調用場景和實現代碼都進行了詳細的註釋,在這裏就不再解釋。

到這裏,我們就完成了Netty的服務端和客戶端的編寫,我們可以看出,使用Netty進行NIO網絡編程要遠比Java原生的NIO編程簡單。

2.3 運行結果

服務端運行結果:
在這裏插入圖片描述
客戶端運行結果:
在這裏插入圖片描述

寫在後面的話:

本書中的案例來自於《Netty權威指南》,但由於Netty版本不同,具體的實現代碼與原書有所不同。

如果喜歡的話希望點贊收藏,關注我,將不間斷更新博客。

希望熱愛技術的小夥伴私聊,一起學習進步

來自於熱愛編程的小白

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章