第一個Netty程式(編寫Hello Netty伺服器)
具體步驟
- 構建一對主從執行緒組
- 定義伺服器啟動類
- 為伺服器設定channel
- 設定處理從執行緒池的助手類初始化器(我們會有一個channel,這個channel會有一堆相應的助手類handler對它進行處理,如編解碼處理、讀寫資料等。這些操作都是需要歸類在助手類的初始化器裡面,簡單來說它就是一個類,在這個類裡要新增很多的助手類。)
- 監聽啟動和關閉伺服器
設定channel初始化器
每一個channel由多個handler共同組成管道(pipeline)。
在下圖,左側有一個channel,右側是一個pipeline。當這個channel註冊完之後,就會有一個pipeline,其實就是一個初始化器。在pipeline裡面,我們需要為它設定很多handler,這些handler我們可以稱之為是一個個的助手類,這些助手類就會針對channel去做一些處理。比如現在有個管道通了,就會分別去註冊handlerA、hanlderB、handlerC。當客戶端和服務端進行互動的時候,相應的助手類會針對我們的請求去做相應的處理。也可以把pipeline當成是一個大的攔截器,然後裡面有很多小的攔截器handler,當請求過來的時候,會一層一層的去攔截。
程式碼實現
1、構建伺服器(HelloServer.java)
/** * 建立人:taofut * 建立時間:2018-12-18 21:50 * 實現客戶端傳送一個請求,伺服器會返回hell netty */ public class HelloServer { public static void main(String[] args) throws InterruptedException { //定義一對執行緒組 //主執行緒組,用於接收客戶端的請求,但是不做任何處理,跟老闆一樣,不做事 EventLoopGroup bossGroup=new NioEventLoopGroup(); //從執行緒組,等待老闆執行緒組安排任務,然後幹活 EventLoopGroup workGroup=new NioEventLoopGroup(); try { //netty伺服器的建立 ServerBootstrap是一個啟動類 ServerBootstrap bootstrap=new ServerBootstrap(); bootstrap.group(bossGroup,workGroup) //設定主從執行緒組 .channel(NioServerSocketChannel.class) //設定nio的雙向通道 .childHandler(new HelloServerInitializer()); //子處理器,用於處理workGroup //啟動server,並且設定埠號為8088,同時啟動方式為同步 ChannelFuture channelFuture=bootstrap.bind(8088).sync(); //監聽關閉的channel,設定為同步方式 channelFuture.channel().closeFuture().sync(); } finally { //關閉主執行緒 bossGroup.shutdownGracefully(); //關閉從執行緒 workGroup.shutdownGracefully(); } } }
2、建立channel初始化器(HelloServerInitializer.java)
/** * 建立人:taofut * 建立時間:2018-12-19 20:22 * 描述:初始化器,channel註冊後,會執行裡面相應的初始化方法 */ public class HelloServerInitializer extends ChannelInitializer<SocketChannel>{ @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //通過SocketChannel獲得對應的管道 ChannelPipeline pipeline=socketChannel.pipeline(); //通過管道,新增handler //HttpServerCodec是由netty官方提供的助手類,可以理解為攔截器 //當請求到服務端,我們需要做解碼,響應到客戶端做編碼 pipeline.addLast("HttpServerCodec",new HttpServerCodec()); //新增自定義的助手類,返回 "hello netty" pipeline.addLast("customHandler",new CustomHandler()); } }
3、新增自定義助手類handler(CustomHandler.java)
/**
* 建立人:taofut
* 建立時間:2018-12-19 20:48
* 描述:建立自定義助手類
*/
//SimpleChannelInboundHandler:對於請求來講,相當於入站、入境
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject>{
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject)
throws Exception {
//獲取channel
Channel channel=channelHandlerContext.channel();
if(httpObject instanceof HttpRequest){
//顯示客戶端的遠端地址
System.out.println(channel.remoteAddress());
//定義傳送的資料訊息
ByteBuf content= Unpooled.copiedBuffer("hello netty", CharsetUtil.UTF_8);
//構建一個http response
FullHttpResponse response=
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
content);
//為響應增加資料型別和長度
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
//把響應刷到客戶端
channelHandlerContext.writeAndFlush(response);
}
}
}
執行測試
啟動HelloServer.java裡的main方法,然後在瀏覽器輸入地址:http://localhost:8088/, “hello netty”被打印出來。
Channel生命週期
將以上的CustomHandler.java類裡面的channel對應的方法一一實現。
/**
* 建立人:taofut
* 建立時間:2018-12-19 20:48
* 描述:建立自定義助手類
*/
//SimpleChannelInboundHandler:對於請求來講,相當於入站、入境
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject>{
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject)
throws Exception {
//獲取channel
Channel channel=channelHandlerContext.channel();
if(httpObject instanceof HttpRequest){
//顯示客戶端的遠端地址
System.out.println(channel.remoteAddress());
//定義傳送的資料訊息
ByteBuf content= Unpooled.copiedBuffer("hello netty", CharsetUtil.UTF_8);
//構建一個http response
FullHttpResponse response=
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
content);
//為響應增加資料型別和長度
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
//把響應刷到客戶端
channelHandlerContext.writeAndFlush(response);
}
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("【channel註冊...】");
super.channelRegistered(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("【channel移除...】");
super.channelUnregistered(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("【channel活躍...】");
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("【channel不活躍...】");
super.channelInactive(ctx);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("【channel資料讀取完畢...】");
super.channelReadComplete(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
System.out.println("【使用者事件觸發...】");
super.userEventTriggered(ctx, evt);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
System.out.println("【channel可寫更改...】");
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("【捕獲異常...】");
super.exceptionCaught(ctx, cause);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("【助手類新增...】");
super.handlerAdded(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("【助手類移除...】");
super.handlerRemoved(ctx);
}
}
然後,我們繼續訪問伺服器,觀察控制檯的列印結果。這裡,我使用了linux命令代替瀏覽器發起請求。(瀏覽器訪問會發起額外的不相關請求,為了保證結果清晰,才使用linux命令代替瀏覽器。)
輸入命令:curl 192.168.3.101(本機IP):8088
控制檯結果:
【助手類新增…】
【channel註冊…】
【channel活躍…】
/192.168.3.101:53147
【channel資料讀取完畢…】
【channel資料讀取完畢…】
【channel不活躍…】
【channel移除…】
【助手類移除…】