netty(八)初識Netty-channel 一、channel

一、channel

1.1 channel的主要方法

1)close() 可以用來關閉 channel
2)closeFuture() 用來處理 channel 的關閉,有如下兩種方式

sync 方法作用是同步等待 channel 關閉
而 addListener 方法是異步等待 channel 關閉

3)pipeline() 方法添加處理器
4)write() 方法將數據寫入
5)writeAndFlush() 方法將數據寫入並刷出

1.2 什麼是channelFuture?

我們看下面一段客戶端代碼,也是前面文章使用的代碼

    public static void main(String[] args) throws InterruptedException {
        Channel channel = new Bootstrap()
                .group(new NioEventLoopGroup(1))
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        System.out.println("init...");
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                .channel(NioSocketChannel.class)
                 .connect("localhost", 8080)
                .sync()
                .channel();

        channel.writeAndFlush("ccc");
        Thread.sleep(1000);
        channel.writeAndFlush("ccc");
    }

主要看到調用connect()方法除,此處返回的其實是一個ChannelFuture 對象,通過channel()方法可以獲得channel對象。

    public ChannelFuture connect(String inetHost, int inetPort) {
        return this.connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
    }
public interface ChannelFuture extends Future<Void> {
    Channel channel();
    ... ...
}

需要注意的是,這個connect方法是一個異步的方法,調用過後實際並沒有建立連接,所以我們得到的ChannelFuture對象中並不能立刻獲得正確的channel。

通過下面的例子看一下現象,啓動一個服務端,端口8080,這裏不提供服務端代碼了,使用前面的就行。啓動我們寫好的測試客戶端代碼:

public class ChannelFutureTest {

    public static void main(String[] args) throws Exception {
        ChannelFuture channelFuture = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) {

                    }
                })
                .connect("localhost", 8080);

        System.out.println(channelFuture.channel());

        //同步等待連接
        channelFuture.sync();
        System.out.println(channelFuture.channel());
    }
}

結果:

[id: 0x7aa12c28]
[id: 0x7aa12c28, L:/127.0.0.1:52286 - R:localhost/127.0.0.1:8080]

如上結果所示,首先打印只有一個id地址,當執行sync()方法,此處會同步阻塞等待連接,如果一直無法連接會拋出超時異常。當成功建立連接後,會繼續執行,並打印出如上結果最後一行的內容。

除使用sync()這個同步方法以外,還有一種異步的方式:

        // 異步
        channelFuture.addListener((ChannelFutureListener) future -> {
            System.out.println(future.channel());
        });

結果:

[id: 0xd9d474f1]
[id: 0xd9d474f1, L:/127.0.0.1:59564 - R:localhost/127.0.0.1:8080]

ChannelFutureListener 會在連接建立時被調用(其中 operationComplete 方法),這裏是一個函數式接口調用。

1.3 什麼是CloseFuture?

我們同通過一段代碼演示一下,此處涉及到channel的close方法,和CloseFuture的close方法。關閉是爲了釋放佔用的資源。

看如下一段代碼:

public class ChannelFutureTest {

    public static void main(String[] args) throws Exception {
        ChannelFuture channelFuture = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                .connect("localhost", 8080);

        // 同步等待連接
        Channel channel = channelFuture.sync().channel();

        new Thread(()->{
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if ("q".equals(line)) {
                    System.out.println("關閉channel");
                    // close 異步操作 1s 之後
                    channel.close();
                    break;
                }
                channel.writeAndFlush(line);
            }
        }, "input").start();

        System.out.println("處理channel關閉後的操作");
}

如上代碼所示,我們的客戶端,允許用戶手動輸入q進行關閉程序,否則就發送內容到服務端。

但是按照如上代碼直接運行客戶端,發現System.out.println("處理channel關閉後的操作");這條命令直接執行了,因爲我們的主要關閉業務邏輯是啓用子線程實現的。

也就是說我們在子線程,即channel還沒有關閉就執行了代碼,這樣可能導致我們的業務邏輯存在問題。

所以我們需要在channel關閉後才進行打印,真實場景中就是channel關閉後進行剩餘業務操作。

我們需要在 System.out.println("處理channel關閉後的操作"); 之前增加以下的代碼:

// 獲取closefuture
ChannelFuture closeFuture = channel.closeFuture();
 //同步阻塞
closeFuture.sync();

上述代碼會獲取到一個closeFuture對象,sync方法會同步阻塞在此,直到子線程當中的channel真正關閉了,纔會繼續執行代碼。

輸入1、2、3、q,直接看結果:

1
2
3
q
關閉channel
處理channel關閉後的操作

與channelFuture相同,closeFuture除了有sync方法進行同步阻塞,仍然也可以使用異步方式進行監聽channel是否關閉的狀態。

將 System.out.println("處理channel關閉後的操作"); 放在以下代碼:

        closeFuture.addListener((ChannelFutureListener) future -> System.out.println("處理channel關閉後的操作"); ); 

輸入1、2、3、q,看結果:

1
2
3
q
關閉channel
處理channel關閉後的操作

提供一個NioEventLoopGroup專門用於關閉。

NioEventLoopGroup group = new NioEventLoopGroup();

通過上面的代碼我們已經能夠成功監測到channel的關閉了,但是相信實踐過朋友們會發現我們channel雖然關閉了,但是整個程序仍然在運行,整體的資源沒有做到全部釋放,這是應爲EventLoopGroup當中的線程沒有停止,這裏需要引入一個方法:

shutdownGracefully()

這個方式是EventLoopGroup當中的方法。我們需要做以下操作,爲了大家看,我把所有的客戶端內容全放在以下代碼中了:

public class ChannelFutureTest {

    public static void main(String[] args) throws Exception {
        // 將group提出來,不能匿名方式,爲了後面調動shutdownGracefully()方法
        NioEventLoopGroup group = new NioEventLoopGroup();
        ChannelFuture channelFuture = new Bootstrap()
                .group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                .connect("localhost", 8080);

        // 同步等待連接
        Channel channel = channelFuture.sync().channel();

        new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if ("q".equals(line)) {
                    System.out.println("關閉channel");
                    // close 異步操作 1s 之後
                    channel.close();
                    break;
                }
                channel.writeAndFlush(line);
            }
        }, "input").start();


        // 處理channel關閉後的操作
        // 獲取 CloseFuture 對象, 1) 同步處理關閉, 2) 異步處理關閉
        ChannelFuture closeFuture = channel.closeFuture();

        //同步
        //closeFuture.sync();

        //異步 - EventLoopGroup線程未關閉
        //closeFuture.addListener((ChannelFutureListener) future -> System.out.println("處理channel關閉後的操作"));

        //異步 - EventLoopGroup線程優雅關閉
        closeFuture.addListener((ChannelFutureListener) future -> group.shutdownGracefully());
    }
}

結果:

1
2
3
q
關閉channel

Process finished with exit code 0

關於channel的介紹就這麼多,有幫助的話點個贊吧。

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