Netty 入門示例詳解
阿新 • • 發佈:2018-10-31
本文導讀
- 在已經瞭解完《Netty 理論詳解》之後,想必已經開始躍躍欲試了,畢竟這麼好的東西呀!
- 本文將詳細講解 Netty 入門案例。
- Netty 官網地址:http://netty.io/
- GitHub 託管地址:https://github.com/netty/netty
- 使用者指南官網地址:https://netty.io/wiki/user-guide.html
- Netty 4.0 官網開發文件地址:https://netty.io/4.0/api/index.html
- Netty 4.1 官網開發文件地址:https://netty.io/4.1/api/index.html
開發包獲取
二進位制 jar 包
- Netty 本身就是人家寫好的一個 Java 的 Jar 包(庫),所以開發的第一步便是要下載它,Maven 方式後面講解。
- 如下所示,進入 Netty 官網,然後滑鼠懸停在 "Downloads" 上,點選下載對應的版本,本文以最新的 4.1.30.Final 為例。
- 下載後壓縮包大小為 20.1M,可以使用 "WinRar" 等壓縮工具進行解壓。
- jar 開發包目錄中提供了各個模組單獨的開發包,同時也提供了對應的原始碼,其中有一個 netty-example-4.1.30.Final.jar 、netty-example-4.1.30.Final-sources.jar 提供了大量的示例,非常適合學習。
- 以匯入二進位制類庫的方式開發 Netty 時,則只需要匯入如下所示的整合包即可,3.7M 包含了所有的模組,同時也提供了原始碼。
Maven 依賴
- 如果使用 Maven 進行專案開發管理,則 Netty 也提供了 Maven 依賴。
- Maven 依賴可以從 Netty 官網下載頁中獲取:https://netty.io/downloads.html,如下所示:
<dependencies>
...
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
<version>X.Y.Z.Q</version>
<scope>compile</scope>
</dependency>
...
</dependencies><artifactId>netty</artifactId>:如果 Netty 是 4.0 以下版本,則 artifactId值寫 netty,如果 Netty 是 4.0 及以上版本,則 寫 netty-all。
<version>X.Y.Z.Q</version>:netty 版本號自己填寫具體版本即可。
- 也可以直接從 Maven 官方倉庫獲取:https://mvnrepository.com/artifact/io.netty/netty-all
- 這裡同樣可以選擇上面最新版的 4.1.30.Final ,點選進入即可獲取依賴:
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.30.Final</version>
</dependency>
Hello World
- 出於入門階段學習的目的,本文將新建 Java SE 專案,採用匯入二進位制開發包的方式,暫時不使用 Maven 管理。
- 環境:IDEA 14 + JDK 8 + Netty4.1.30。
- 專案建好之後,新建 lib 目錄用於存放第三方開發包,注意要新增到類路徑中,同時新建 classes 目錄作為專案統一的編譯輸出目錄。
- 以一個簡單的例子來對 Netty 網路程式設計有一個初步的瞭解,其中的細節可以以後慢慢消化:先開啟伺服器等待客戶端連線,然後開啟客戶端,同時給伺服器傳送一條訊息,伺服器接收到訊息後,回發一條訊息。
服務端
·TimeServerHandler·
package com.lct.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
/**
* Created by Administrator on 2017/5/16.
* 用於對網路事件進行讀寫操作
*/
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
/**獲取緩衝區可讀位元組數,並建立陣列*/
byte[] reg = new byte[buf.readableBytes()];
/**將緩衝區位元組陣列複製到新建的byte陣列中*/
buf.readBytes(reg);
/**獲取請求訊息*/
String body = new String(reg, "UTF-8");
System.out.println("The server receive order : " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
/**給客戶端傳送應答訊息,實際是將訊息放到傳送緩衝陣列中*/
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
/**將傳送緩衝區中的訊息全部寫到SocketChannel中*/
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
/**當發生異常時,關閉ChannelHandlerContext,釋放和它相關聯的控制代碼等資源*/
ctx.close();
}
}
·TimeServer·
package com.lct.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.beans.beancontext.BeanContextChildComponentProxy;
/**
* Created by Administrator on 2017/5/16.
*/
public class TimeServer {
public void bind(int port){
/**配置服務端的NIO執行緒組,專門用於網路事件處理
* 一個用於服務端結束客戶端連線
* 一個用於用於進行SocketChannel讀寫*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
/**ServerBootstrap是Netty用於啟動NIO服務端的輔助啟動類*/
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChildChannelHandler());
/**繫結埠,同步等待成功*/
ChannelFuture f = b.bind(port).sync();
/**等待伺服器監聽埠關閉*/
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
/**優雅退出,釋放執行緒池資源*/
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
arg0.pipeline().addLast(new TimeServerHandler());
}
}
public static void main(String[] args) {
int port = 8080;
if (args!=null && args.length>0){
try {
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
/**採用預設值*/
}
}
new TimeServer().bind(port);
}
}
客戶端
·TimeClientHandler·
package com.lct.client;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.logging.Logger;
/**
* Created by Administrator on 2017/5/17.
* 用於對網路事件進行讀寫操作
*/
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
private final ByteBuf firstMessage;
public TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}
/**當客戶端和服務端TCP鏈路建立成功之後,Netty的NIO執行緒會呼叫channelActive方法
* 同時傳送查詢時間的指令給服務端,呼叫ChannelHandlerContext的writeAndFlush方法
* 將 請求訊息傳送給服務端*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(firstMessage);
}
@Override
/**當服務端返回應答訊息時,channelRead方法被呼叫,從Netty的ByteBuf中讀取並列印應答訊息*/
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8");
System.out.println("Server return Message:"+body);
}
/**當發生異常時,列印異常 日誌,釋放客戶端資源*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
/**釋放資源*/
logger.warning("Unexpected exception from downstream : "+cause.getMessage());
ctx.close();
}
}
·TimeClient·
package com.lct.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Created by Administrator on 2017/5/16.
*/
public class TimeClient {
public void connect(int port,String host){
/**配置客戶端NIO執行緒組*/
EventLoopGroup group = new NioEventLoopGroup();
try {
/**建立客戶端輔助啟動類,並對其配置
* 與伺服器稍微不同,這裡的Channel設定為NioSocketChannel
* 然後為其新增Handler,這裡直接使用匿名內部類,實現initChannel方法
* 作用是當建立NioSocketChannel成功後,在進行初始化時
* 將它的ChannelHandler設定到ChannelPipeline中,用於處理網路I/O事件*/
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
/**發起非同步連線操作*/
ChannelFuture f = b.connect(host, port);
/**等待客戶端鏈路關閉*/
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
/**優雅退出,釋放NIO執行緒組*/
group.shutdownGracefully();
}
}
public static void main(String[] args) {
int port = 8080;
if (args!=null && args.length>0){
try {
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
/**採用預設值*/
}
}
new TimeClient().connect(port,"127.0.0.1");
}
}
測試執行
- 先執行服務端,再執行客戶端:
自學建議
- 對於和我一樣自學的兄弟姐妹來說,除了百度以外,還可以參考下載包中更多官方示例,如下所示:
- 也可以參考官網的使用者手冊:https://netty.io/wiki/user-guide-for-4.x.html
- 也可以購買書籍,或者網上的 PDF 文件,如下所示的《Netty 權威指南》可以下載:https://download.csdn.net/download/wangmx1993328/10717896
············下一篇《傳統 BIO 程式設計》