初識netty之永遠的helloworld
阿新 • • 發佈:2021-11-18
一、Netty概述
官方的介紹:
Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.
Netty是 一個非同步事件驅動的網路應用程式框架,用於快速開發可維護的高效能協議伺服器和客戶端。
二、為什麼使用Netty
從官網上介紹,Netty是一個網路應用程式框架,開發伺服器和客戶端。也就是用於網路程式設計的一個框架。既然是網路程式設計,Socket就不談了,為什麼不用NIO呢?
2.1 NIO的缺點
- NIO的類庫和API繁雜,學習成本高,你需要熟練掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
- 需要熟悉Java多執行緒程式設計。這是因為NIO程式設計涉及到Reactor模式,你必須對多執行緒和網路程式設計非常熟悉,才能寫出高質量的NIO程式
- 臭名昭著的epoll bug。它會導致Selector空輪詢,最終導致CPU 100%。直到JDK1.7版本依然沒得到根本性的解決
2.2 Netty的優點
- API使用簡單,學習成本低。
- 功能強大,內建了多種解碼編碼器,支援多種協議。
- 效能高,對比其他主流的NIO框架,Netty的效能最優。
- 社群活躍,發現BUG會及時修復,迭代版本週期短,不斷加入新的功能。
- Dubbo、Elasticsearch都採用了Netty,質量得到驗證。
三、架構
- 綠色的部分Core核心模組,包括零拷貝、API庫、可擴充套件的事件模型。
- 橙色部分Protocol Support協議支援,包括Http協議、webSocket、SSL(安全套接字協議)、谷歌Protobuf協議、zlib/gzip壓縮與解壓縮、Large File Transfer大檔案傳輸等等
- 紅色的部分Transport Services傳輸服務,包括Socket、Datagram、Http Tunnel等等。
以上可看出Netty的功能、協議、傳輸方式都比較全,比較強大。
四、業界使用netty
五、搭建永遠的 Hello Word
5.1 引入Maven依賴
使用的版本是4.1.20,相對比較穩定的一個版本。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<netty-all.version>4.1.20.Final</netty-all.version>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-all.version}</version>
</dependency>
</dependencies>
5.2程式碼編寫
專案結構
netty-helloworld
├── client
├── Client.class -- 客戶端啟動類
├── ClientHandler.class -- 客戶端邏輯處理類
├── ClientHandler.class -- 客戶端初始化類
├── server
├── Server.class -- 服務端啟動類
├── ServerHandler -- 服務端邏輯處理類
├── ServerInitializer -- 服務端初始化類
服務端程式碼
/**
* @author wuzhixuan
* @version 1.0.0
* @ClassName Server.java
* @Description netty 服務端
* @createTime 2021年11月18日 11:22:00
*/
public class Server {
public static void main(String[] args) {
/* 建立兩個EventLoopGroup物件*/
// 建立boss執行緒組 用於服務端介面客戶端的連結
EventLoopGroup boss = new NioEventLoopGroup();
// 建立worker執行緒組,用於客戶端的進行SocketChannel資料讀寫
EventLoopGroup worker = new NioEventLoopGroup();
try {
// 建立 ServerBootstrap 物件,
ServerBootstrap bootstrap = new ServerBootstrap();
//設定使用的EventLoopGroup
bootstrap.group(boss, worker)
//設定要被例項化的為 NioServerSocketChannel 類
.channel(NioServerSocketChannel.class)
// 設定 NioServerSocketChannel 的處理器
.handler(new LoggingHandler(LogLevel.INFO))
// 設定連入服務端的 Client 的 SocketChannel 的處理器
.childHandler(new ServerInitializer());
// 繫結埠,啟動客戶端
final ChannelFuture bind = bootstrap.bind(8888);
// 監聽服務端關閉,並阻塞等待
bind.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
連入服務端的 Client 的 SocketChannel 的處理器
/**
* @author wuzhixuan
* @version 1.0.0
* @ClassName ServerInitializer.java
* @Description 連入服務端的 Client 的 SocketChannel 的處理器
* @createTime 2021年11月18日 11:41:00
*/
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
private static final StringDecoder DECODER = new StringDecoder();
private static final StringEncoder ENCODER = new StringEncoder();
private static final ServerHandler SERVER_HANDLER = new ServerHandler();
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
final ChannelPipeline pipeline = socketChannel.pipeline();
// 新增幀限定符來防止粘包現象
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(DECODER);
pipeline.addLast(ENCODER);
// 業務邏輯實現類
pipeline.addLast(SERVER_HANDLER);
}
}
服務端具體業務邏輯
/**
* @author wuzhixuan
* @version 1.0.0
* @ClassName ServerHandler.java
* @Description TODO
* @createTime 2021年11月18日 11:48:00
*
* 使用Netty編寫業務層的程式碼,我們需要繼承ChannelInboundHandlerAdapter 或SimpleChannelInboundHandler類,
* 在這裡順便說下它們兩的區別吧。
* 1.繼承SimpleChannelInboundHandler類之後,會在接收到資料後會自動release掉資料佔用的Bytebuffer資源。並且繼承該類需要指定資料格式。
* 2.繼承ChannelInboundHandlerAdapter則不會自動釋放,需要手動呼叫ReferenceCountUtil.release()等方法進行釋放。繼承該類不需要指定資料格式。
* 個人推薦服務端繼承ChannelInboundHandlerAdapter,手動進行釋放,防止資料未處理完就自動釋放了。而且服務端可能有多個客戶端進行連線,並且每一個客戶端請求的資料格式都不一致,這時便可以進行相應的處理。
* 客戶端根據情況可以繼承SimpleChannelInboundHandler類。好處是直接指定好傳輸的資料格式,就不需要再進行格式的轉換了
*/
public class ServerHandler extends SimpleChannelInboundHandler<String> {
/**
* 建立連線時,傳送一條慶祝訊息
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.write("Welcome to " + InetAddress.getLocalHost().getHostName() + "!\r\n");
ctx.write("It is " + new Date() + " now.\r\n");
ctx.flush();
}
/**
* 業務邏輯處理
* @param ctx
* @param request
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String request) throws Exception {
String response;
boolean close = false;
if (request.isEmpty()) {
response = "Please type something.\r\n";
} else if ("bye".equals(request.toLowerCase())) {
response = "Have a good day!\r\n";
close = true;
} else {
response = "Did you say '" + request + "'?\r\n";
}
ChannelFuture future = ctx.write(response);
if (close) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
/**
* 異常處理
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
}
客戶端
客戶端主類
/**
* @author wuzhixuan
* @version 1.0.0
* @ClassName Client.java
* @Description 客戶端程式碼實現
* @createTime 2021年11月18日 11:56:00
*/
public class Client {
public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ClientInitializer());
Channel ch = bootstrap.connect("127.0.0.1",8888).sync().channel();
ChannelFuture lastWriteFuture = null;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
for (;;) {
String line = in.readLine();
if (line == null) {
break;
}
// Sends the received line to the server.
lastWriteFuture = ch.writeAndFlush(line + "\r\n");
// If user typed the 'bye' command, wait until the server closes
// the connection.
if ("bye".equals(line.toLowerCase())) {
ch.closeFuture().sync();
break;
}
}
// Wait until all messages are flushed before closing the channel.
if (lastWriteFuture != null) {
lastWriteFuture.sync();
}
} catch (InterruptedException | IOException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
/**
* @author wuzhixuan
* @version 1.0.0
* @ClassName ClientHandler.java
* @Description TODO
* @createTime 2021年11月18日 14:12:00
*/
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
System.err.println(msg);
}
//異常資料捕獲
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
/**
* @author wuzhixuan
* @version 1.0.0
* @ClassName ClientInitializer.java
* @Description TODO
* @createTime 2021年11月18日 14:10:00
*/
public class ClientInitializer extends ChannelInitializer<SocketChannel> {
private static final StringDecoder DECODER = new StringDecoder();
private static final StringEncoder ENCODER = new StringEncoder();
private static final ClientHandler CLIENT_HANDLER = new ClientHandler();
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(DECODER);
pipeline.addLast(ENCODER);
pipeline.addLast(CLIENT_HANDLER);
}
}
到這裡netty就簡單搭建完成了
服務端輸出
十一月 18, 2021 2:15:23 下午 io.netty.handler.logging.LoggingHandler channelRegistered
資訊: [id: 0x54ec564b] REGISTERED
十一月 18, 2021 2:15:24 下午 io.netty.handler.logging.LoggingHandler bind
資訊: [id: 0x54ec564b] BIND: 0.0.0.0/0.0.0.0:8888
十一月 18, 2021 2:15:24 下午 io.netty.handler.logging.LoggingHandler channelActive
資訊: [id: 0x54ec564b, L:/0:0:0:0:0:0:0:0:8888] ACTIVE
十一月 18, 2021 2:15:35 下午 io.netty.handler.logging.LoggingHandler channelRead
資訊: [id: 0x54ec564b, L:/0:0:0:0:0:0:0:0:8888] RECEIVED: [id: 0xb0450fea, L:/127.0.0.1:8888 - R:/127.0.0.1:50272]
客戶端輸出
Connected to the target VM, address: '127.0.0.1:50101', transport: 'socket'
Welcome to WuZhiXuan!
It is Thu Nov 18 14:15:35 CST 2021 now.
hello
Did you say 'hello'?
Please type something.
Please type something.
s
Did you say 's'?
Please type something.
sad
Did you say 'sad'?
asd
Did you say 'asd'?
s
Did you say 's'?