Hello World
目標
開發一個簡單的伺服器端和客戶端
- 客戶端向伺服器端傳送 hello, world
- 伺服器僅接收,不返回
依賴
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
伺服器端
@Slf4j
public class HelloServer {
public static void main(String[] args) {
new ServerBootstrap()
// 1. 建立 NioEventLoopGroup
.group(new NioEventLoopGroup())
// 2. 選擇 Socket 實現類,NioServerSocketChannel 是基於 nio 實現的
.channel(NioServerSocketChannel.class)
// 3. 子執行緒處理類 handler
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
// 5. socketChannel 解碼器
nioSocketChannel.pipeline().addLast(new StringDecoder());
// 6. SocketChannel 的業務處理,使用上一個處理器的處理結果
nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
log.info("msg = " + msg);
}
});
}
})
// 4. 繫結監聽埠
.bind(8080);
}
}
程式碼解讀
- 1 處,建立 NioEventLoopGroup,可以簡單理解為 執行緒池 + Selector 後面會詳細展開
- 2 處,選擇服務 Scoket 實現類,其中 NioServerSocketChannel 表示基於 NIO 的伺服器端實現
- 3 處,為啥方法叫 childHandler,是接下來新增的處理器都是給 SocketChannel 用的,而不是給 ServerSocketChannel。ChannelInitializer 處理器(僅執行一次),它的作用是待客戶端 SocketChannel 建立連線後,執行 initChannel 以便新增更多的處理器
- 4 處,ServerSocketChannel 繫結的監聽埠
- 5 處,SocketChannel 的處理器,解碼 ByteBuf => String
- 6 處,SocketChannel 的業務處理器,使用上一個處理器的處理結果
客戶端
@Slf4j
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
// 1. 啟動類
new Bootstrap()
// 2. 新增 EventLoop
.group(new NioEventLoopGroup())
// 3. 選擇客戶端 channel 實現
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
// 8
nioSocketChannel.pipeline().addLast(new StringEncoder());
}
})
// 4. 連線到伺服器
.connect(new InetSocketAddress("localhost",8080))
// 5
.sync()
// 6
.channel()
// 7. 向伺服器傳送資料
.writeAndFlush("hello word");
}
}
程式碼解讀
- 1 處,建立 NioEventLoopGroup,同 Server
- 2 處,選擇客戶 Socket 實現類,NioSocketChannel 表示基於 NIO 的客戶端實現
- 3 處,新增 SocketChannel 的處理器,ChannelInitializer 處理器(僅執行一次),它的作用是待客戶端 SocketChannel 建立連線後,執行 initChannel 以便新增更多的處理器
- 4 處,指定要連線的伺服器和埠
- 5 處,Netty 中很多方法都是非同步的,如 connect,這時需要使用 sync 方法等待 connect 建立連線完畢
- 6 處,獲取 channel 物件,它即為通道抽象,可以進行資料讀寫操作
- 7 處,寫入訊息並清空緩衝區
- 8 處,訊息會經過通道 handler 處理,這裡是將 String => ByteBuf 發出
- 資料經過網路傳輸,到達伺服器端,伺服器端 5 和 6 處的 handler 先後被觸發,走完一個流程
元件
EventLoop
事件迴圈物件
EventLoop 本質是一個單執行緒執行器(同時維護了一個 Selector),裡面有 run 方法處理 Channel 上源源不斷的 io 事件。
它的繼承關係比較複雜
- 一條線是繼承自 j.u.c.ScheduledExecutorService 因此包含了執行緒池中所有的方法
- 另一條線是繼承自 netty 自己的 OrderedEventExecutor,
- 提供了 boolean inEventLoop(Thread thread) 方法判斷一個執行緒是否屬於此 EventLoop
- 提供了 parent 方法來看看自己屬於哪個 EventLoopGroup
事件迴圈組
EventLoopGroup 是一組 EventLoop,Channel 一般會呼叫 EventLoopGroup 的 register 方法來繫結其中一個 EventLoop,後續這個 Channel 上的 io 事件都由此 EventLoop 來處理(保證了 io 事件處理時的執行緒安全)
- 繼承自 netty 自己的 EventExecutorGroup
- 實現了 Iterable 介面提供遍歷 EventLoop 的能力
- 另有 next 方法獲取集合中下一個 EventLoop
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
/**
* 細分1:
* boss 只負責 ServerSocketChannel 上的 accept 事件
* worker 只負責 SocketChannel 上的讀寫事件
*/
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
/**
* 細分2:
* 建立一個獨立的 EventLoopGroup
*/
EventLoopGroup dfGroup = new DefaultEventLoopGroup();
new ServerBootstrap()
.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel sc) throws Exception {
sc.pipeline().addLast("handler1",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.info(buf.toString(Charset.defaultCharset()));
ctx.fireChannelRead(msg); // 把訊息傳遞給下一個 handler
}
}).addLast(dfGroup,"handler2",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
log.info(buf.toString(Charset.defaultCharset()));
}
});
}
})
.bind(8085);
}
}
@Slf4j
public class EventLoopClient {
public static void main(String[] args) throws InterruptedException {
Channel channel = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel sc) throws Exception {
sc.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8085))
.sync()
.channel();
log.info(channel.toString());
System.out.println();
}
}
優雅關閉
優雅關閉 shutdownGracefully 方法。該方法會首先切換 EventLoopGroup 到關閉狀態從而拒絕新的任務的加入,然後在任務佇列的任務都處理完成後,停止執行緒的執行。從而確保整體應用是在正常有序的狀態下退出的
Channel
channel 的主要作用
- close() 可以用來關閉 channel
- closeFuture() 用來處理 channel 的關閉
- sync 方法作用是同步等待 channel 關閉
- 而 addListener 方法是非同步等待 channel 關閉
- pipeline() 方法新增處理器
- write() 方法將資料寫入
- writeAndFlush() 方法將資料寫入並刷出
ChannelFuture
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
new ServerBootstrap()
.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel sc) throws Exception {
sc.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.info(buf.toString(Charset.defaultCharset()));
}
});
}
})
.bind(8086);
}
}
@Slf4j
public class EventLoopClient {
public static void main(String[] args) throws InterruptedException {
// 2. 帶有 Future ,Promise 的型別都是和非同步方法配套使用,用來處理結果
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel sc) throws Exception {
sc.pipeline().addLast(new StringEncoder());
}
})
// 1. 連線到伺服器
// 非同步阻塞,main 執行緒發起了呼叫,真正執行 connect 連線的是 nio 執行緒
.connect(new InetSocketAddress("localhost", 8086));
// 2.1 使用 sync 方法同步處理結果
// channelFuture.sync(); // 阻塞住當前執行緒,直到 nio 執行緒連線建立完畢
// Channel channel = channelFuture.channel();
// log.info(channel.toString());
// channel.writeAndFlush("hello word;");
// 2.2 使用 addListener(回撥物件)方法非同步處理結果
channelFuture.addListener(new ChannelFutureListener() {
// 在 nio 執行緒連線建立好之後,會呼叫 operationComplete
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
Channel channel = channelFuture.channel();
log.info(channel.toString());
channel.writeAndFlush("hello word");
}
});
}
}
CloseFuture
@Slf4j
public class EventLoopClient2 {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
ChannelFuture channelFuture = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
socketChannel.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8086));
Channel channel = channelFuture.sync().channel();
new Thread(() -> {
Scanner scanner = new Scanner(System.in);
while (true) {
String str = scanner.nextLine();
if("q".equals(str)){
channel.close();
break;
}
channel.writeAndFlush(str);
}
},"input").start();
// 1. 獲取 CloseFuture 物件同步處理關閉
ChannelFuture closeFuture = channel.closeFuture();
// closeFuture.sync(); // 阻塞
// log.info("處理關閉後的操作...");
// 2. 非同步處理關閉
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
log.info("處理關閉之後的操作...");
// 優雅的停止接受任務
group.shutdownGracefully();
}
});
}
}