1. 程式人生 > >【Netty入門】基於Netty的Server / Client

【Netty入門】基於Netty的Server / Client

回顧NIO

我在之前介紹了NIO的server與client,雖然說程式設計相對於BIO比較複雜,但是效能高啊,而且Netty的server/client也是基於NIO的。所以,在介紹Netty的server/client 之前,先回顧一下NIO 的server / Client的步驟還是很有必要的!

1.NIO 服務端Server開發步驟:

(1)建立ServerSocketChannel,配置它為非阻塞模式;

(2)繫結監聽,配置TCP引數,例如:backlog大小;

(3)建立一個獨立的I/O執行緒,用於輪詢多路複用器Selector;

(4)建立Selector,將之前建立的ServerSocketChannel註冊到Selector上,監聽SelectorKey.ACCEPT;

(5)啟動I/O,在迴圈體中執行Selector.select() 方法,輪詢就緒的Channel;

(6)當輪詢到了處於就緒狀態的Channel時,需要對其進行判斷,如果是OP_ACCEPT狀態,說明是新的客戶端接入,則呼叫ServerSocketChannel.accept() 方法接受新的客戶端;

(7)設定新接入的客戶端鏈路SocketChannel為非阻塞模式,配置其他的一些TCP引數;

(8)將SocketChannel註冊到Selector,監聽OP_READ操作位;

(9)如果輪詢的Channel為OP_READ,則說明SocketChannel中有新的就緒的資料包需要讀取,則構造ByteBuffer物件,讀取資料包;

(10)如果輪詢的Channel為OP_WRITE,說明還有資料沒有傳送完成,需要繼續傳送。

2.NIO 客戶端client開發步驟:

(1)開啟SocketChannel,配置它為非阻塞模式,同時設定TCP引數

(2)非同步連線到服務端,若連線成功,就向多路複用器註冊讀事件OP_READ,否則註冊OP_CONNECT事件,請求再次連線。

(3)建立Selector,啟動執行緒;

(4)Selector輪詢就緒的SelectionKey

(5)若SelectionKey是連線事件,則判斷連線是否成功,若連線成功,向多路複用器註冊讀事件OP_READ;

(6)若SelectionKey是可讀事件,就建立ByteBuffer,用於讀取資料;

(7)對ByteBuffer進行編解碼;

(8)非同步寫ByteBuffer到SocketChannel。

小結:
你是不是感到“巨複雜”,步驟怎麼這麼多,直接使用NIO程式設計的步驟的確很繁瑣,所以,Netty應運而生,它簡化了步驟,更易上手,我將在接下來介紹Netty的Server / Client程式。

版本 :基於Netty 4.1.11

Netty的服務端程式碼

public class Server4 {
    public static void main(String[] args) throws SigarException {

        //boss執行緒監聽埠,worker執行緒負責資料讀寫
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();

        try{
            //輔助啟動類
            ServerBootstrap bootstrap = new ServerBootstrap();
            //設定執行緒池
            bootstrap.group(boss,worker);

            //設定socket工廠
            bootstrap.channel(NioServerSocketChannel.class);

            //設定管道工廠
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    //獲取管道
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    //字串解碼器
                    pipeline.addLast(new StringDecoder());
                    //字串編碼器
                    pipeline.addLast(new StringEncoder());
                    //處理類
                    pipeline.addLast(new ServerHandler4());
                }
            });

            //設定TCP引數
            //1.連結緩衝池的大小(ServerSocketChannel的設定)
            bootstrap.option(ChannelOption.SO_BACKLOG,1024);
            //維持連結的活躍,清除死連結(SocketChannel的設定)
            bootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
            //關閉延遲傳送
            bootstrap.childOption(ChannelOption.TCP_NODELAY,true);

            //繫結埠
            ChannelFuture future = bootstrap.bind(8866).sync();
            System.out.println("server start ...... ");

            //等待服務端監聽埠關閉
            future.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //優雅退出,釋放執行緒池資源
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

}


class ServerHandler4 extends SimpleChannelInboundHandler<String> {

    //讀取客戶端傳送的資料
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("client response :"+msg);
        ctx.channel().writeAndFlush("i am server !");

//        ctx.writeAndFlush("i am server !").addListener(ChannelFutureListener.CLOSE);
    }

    //新客戶端接入
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive");
    }

    //客戶端斷開
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelInactive");
    }

    //異常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //關閉通道
        ctx.channel().close();
        //列印異常
        cause.printStackTrace();
    }
}

Netty的客戶端程式碼

public class Client4 {

    public static void main(String[] args) {

        //worker負責讀寫資料
        EventLoopGroup worker = new NioEventLoopGroup();

        try {
            //輔助啟動類
            Bootstrap bootstrap = new Bootstrap();

            //設定執行緒池
            bootstrap.group(worker);

            //設定socket工廠
            bootstrap.channel(NioSocketChannel.class);

            //設定管道
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    //獲取管道
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    //字串解碼器
                    pipeline.addLast(new StringDecoder());
                    //字串編碼器
                    pipeline.addLast(new StringEncoder());
                    //處理類
                    pipeline.addLast(new ClientHandler4());
                }
            });

            //發起非同步連線操作
            ChannelFuture futrue = bootstrap.connect(new InetSocketAddress("127.0.0.1",8866)).sync();

            //等待客戶端鏈路關閉
            futrue.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //優雅的退出,釋放NIO執行緒組
            worker.shutdownGracefully();
        }
    }

}

class ClientHandler4 extends SimpleChannelInboundHandler<String> {

    //接受服務端發來的訊息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("server response : "+msg);
    }

    //與伺服器建立連線
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //給伺服器發訊息
        ctx.channel().writeAndFlush("i am client !");

        System.out.println("channelActive");
    }

    //與伺服器斷開連線
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelInactive");
    }

    //異常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //關閉管道
        ctx.channel().close();
        //列印異常資訊
        cause.printStackTrace();
    }

}

服務端執行結果:

這裡寫圖片描述



客戶端執行結果:
這裡寫圖片描述

注:我使用的Netty的版本為Netty 4.1.11,Netty各個版本之間的程式碼或有些許差異,請注意。

小結:可以明確感覺到Netty的伺服器客戶端程式明顯看起來順眼了不少,當然,它比NIO用起來順手多了。同時,Netty的效能也是很高的。



本人才疏學淺,若有錯誤,請指出
謝謝!