一、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的介紹就這麼多,有幫助的話點個贊吧。