引言
前面我們介紹了網路一些基本的概念,雖然說這些很難吧,但是至少要做到理解吧。有了之前的基礎,我們來正式揭開Netty這神祕的面紗就會簡單很多。
服務端
public class PrintServer {
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); //1
EventLoopGroup workerGroup = new NioEventLoopGroup(); //2
try {
ServerBootstrap b = new ServerBootstrap(); //3
b.group(bossGroup, workerGroup) //4
.channel(NioServerSocketChannel.class) //5
.option(ChannelOption.SO_BACKLOG, 1024) //6
.childHandler(new ChannelInitializer<SocketChannel>() { //7
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new PrintServerHandler());
}
});
ChannelFuture f = b.bind(port).sync(); //8
f.channel().closeFuture().sync(); //9
} finally {
// 優雅退出,釋放執行緒池資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 8080;
new TimeServer().bind(port);
}
}
我們來分析一下上面的這段程式碼(下面的每一點對應上面的註釋)
1~2:首先我們建立了兩個NioEventLoopGroup例項,它是一個由Netty封裝好的包含NIO的執行緒組。為什麼建立兩個?我想經過前面的學習大家應該都清楚了。對,因為Netty的底層是IO多路複用,bossGroup 是用於接收客戶端的連線,原理就是一個實現的Selector的Reactor執行緒。而workerGroup用於進行SocketChannel的網路讀寫。
3:建立一個ServerBootstrap物件,可以把它想象成Netty的入口,通過這類來啟動Netty,將所需要的引數傳遞到該類當中,大大降低了的開發難度。
4:將兩個NioEventLoopGroup例項繫結到ServerBootstrap物件中。
5:建立Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),這裡建立的是NIOserverSocketChannel,它的功能可以理解為當接受到客戶端的連線請求的時候,完成TCP三次握手,TCP物理鏈路建立成功。並將該“通道”與workerGroup執行緒組的某個執行緒相關聯。
6:設定引數,這裡設定的SO_BACKLOG,意思是客戶端連線等待佇列的長度為1024.
7:建立連線後的具體Handler。就是我們接受資料後的具體操作,例如:記錄日誌,對資訊解碼編碼等。
8:繫結埠,同步等待成功
9:等待服務端監聽埠關閉
繫結該服務端的Handler
public class PrintServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf = (ByteBuf) msg; //1
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req); //將快取區的位元組陣列複製到新建的req陣列中
String body = new String(req, "UTF-8");
System.out.println(body);
String response= "列印成功";
ByteBuf resp = Unpooled.copiedBuffer(response.getBytes());
ctx.write(resp); //2
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush(); //3
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}
PrintServerHandler 繼承 ChannelHandlerAdapter ,在這裡它的功能為 列印客戶端發來的資料並且返回客戶端列印成功。
我們只需要實現channelRead,exceptionCaught,前一個為接受訊息具體邏輯的實現,後一個為發生異常後的具體邏輯實現。
1:我們可以看到,接受的訊息被封裝為了Object ,我們將其轉換為ByteBuf ,前一章的講解中也說明了該類的作用。我們需要讀取的資料就在該快取類中。
2~3:我們將寫好的資料封裝到ByteBuf中,然後通過write方法寫回到客戶端,這裡的3呼叫flush方法的作用為,防止頻繁的傳送資料,write方法並不直接將資料寫入SocketChannel中,而是把待傳送的資料放到傳送快取陣列中,再呼叫flush方法傳送資料。
客戶端
public class PrintClient {
public void connect(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup(); //1
try {
Bootstrap b = new Bootstrap(); //2
b.group(group) //3
.channel(NioSocketChannel.class) //4
.option(ChannelOption.TCP_NODELAY, true) //5
.handler(new ChannelInitializer<SocketChannel>() { //6
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync(); //7
f.channel().closeFuture().sync(); //8
} finally {
// 優雅退出,釋放NIO執行緒組
group.shutdownGracefully();
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 8080;
new TimeClient().connect(port, "127.0.0.1");
}
}
我們繼續來分析一下上面的這段程式碼(下面的每一點對應上面的註釋)
1:區別於服務端,我們在客戶端只建立了一個NioEventLoopGroup例項,因為客戶端你並不需要使用I/O多路複用模型,需要有一個Reactor來接受請求。只需要單純的讀寫資料即可
2:區別於服務端,我們在客戶端只需要建立一個Bootstrap物件,它是客戶端輔助啟動類,功能類似於ServerBootstrap。
3:將NioEventLoopGroup例項繫結到Bootstrap物件中。
4:建立Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),區別與服務端,這裡建立的是NIOSocketChannel.
5:設定引數,這裡設定的TCP_NODELAY為true,意思是關閉延遲傳送,一有訊息就立即傳送,預設為false。
6:建立連線後的具體Handler。注意這裡區別與服務端,使用的是handler()而不是childHandler()。handler和childHandler的區別在於,handler是接受或傳送之前的執行器;childHandler為建立連線之後的執行器。
7:發起非同步連線操作
8:當代客戶端鏈路關閉
繫結該客戶端的Handler
public class PrintClientHandler extends ChannelHandlerAdapter {
private static final Logger logger = Logger
.getLogger(TimeClientHandler.class.getName());
private final ByteBuf firstMessage;
/**
* Creates a client-side handler.
*/
public TimeClientHandler() {
byte[] req = "你好服務端".getBytes();
firstMessage = Unpooled.buffer(req.length); //1
firstMessage.writeBytes(req);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(firstMessage); //2
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) //3
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("服務端回應訊息 : " + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { //4
// 釋放資源
System.out.println("Unexpected exception from downstream : "
+ cause.getMessage());
ctx.close();
}
}
PrintClientHandler 繼承 ChannelHandlerAdapter ,在這裡它的功能為 傳送資料並列印服務端發來的資料。
我們只需要實現channelActive,channelRead,exceptionCaught,第一個為建立連線後立即執行,後兩個與一個為接受訊息具體邏輯的實現,另一個為發生異常後的具體邏輯實現。
1:將傳送的資訊封裝到ByteBuf中。
2:傳送訊息。
3:接受客戶端的訊息並列印
4:發生異常時,列印異常資訊,釋放客戶端資源
總結
這是一個入門程式,對應前面所講的I/O多路複用模型以及NIO的特性,能很有效的理解該模式的程式設計方式。如果這幾段程式碼看著很費勁,那麼可以看看之前博主的Netty基礎系列。
如果博主哪裡說得有問題,希望大家提出來,一起進步~