netty(2)---搭建一個簡單服務器
效果
:當用戶訪問:localhost:8088 后 服務器返回 “hello netty”;
一、服務端線程模型
下面的做法是服務端監聽線程和 IO 線程分離,類似於 Reactor 的多線程模型,它的工作原理圖如下(盜的圖):
這里netty版本是4.1.25
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
二、主程序類
/**
* @Description: 實現客戶端發送一個請求,服務器會返回 hello netty
*/
public class HelloServer {
public static void main(String[] args) throws Exception {
// 定義一對線程組
// 主線程組, 用於接受客戶端的連接,但是不做任何處理,跟老板一樣,不做事
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 從線程組, 老板線程組會把任務丟給他,讓手下線程組去做任務
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// netty服務器的創建, 輔助工具類,用於服務器通道的一系列配置
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup) //綁定兩個線程組
.channel(NioServerSocketChannel.class) //指定NIO的模式
.childHandler(new HelloServerInitializer()); // 子處理器,用於處理workerGroup
// 啟動server,並且設置8088為啟動的端口號,同時啟動方式為同步
ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();
// 監聽關閉的channel,設置位同步方式
channelFuture.channel().closeFuture().sync();
} finally {
//退出線程組
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
上面這段代碼展示了服務端的一個基本步驟:
(1)、 初始化用於Acceptor的主"線程池"以及用於I/O工作的從"線程池";
(2)、 初始化ServerBootstrap實例, 此實例是netty服務端應用開發的入口;
(3)、 通過ServerBootstrap的group方法,設置(1)中初始化的主從"線程池";
(4)、 指定通道channel的類型,由於是服務端,故而是NioServerSocketChannel;
(5)、 設置ServerSocketChannel的處理器
(6)、 設置子通道也就是SocketChannel的處理器, 其內部是實際業務開發的"主戰場"
(8)、 配置子通道也就是SocketChannel的選項
(9)、 綁定並偵聽某個端口
三、子處理器 HelloServerInitializer類
/**
* @Description: 初始化器,channel注冊后,會執行里面的相應的初始化方法
*/
public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 通過SocketChannel去獲得對應的管道
ChannelPipeline pipeline = channel.pipeline();
// 通過管道,添加handler
// HttpServerCodec是由netty自己提供的助手類,可以理解為攔截器
// 當請求到服務端,我們需要做解碼,響應到客戶端做編碼
pipeline.addLast("HttpServerCodec", new HttpServerCodec());
// 添加自定義的助手類,返回 "hello netty~"
pipeline.addLast("customHandler", new CustomHandler());
}
}
子處理器也可以通過內部方法來實現的。
b.group(group).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//這里進行方法實現.......
socketChannel.pipeline().addLast(serverHandler);
}
});
四、自定義助手類 CustomHandler類
/**
* 創建自定義助手類
*/
// SimpleChannelInboundHandler: 對於請求來講,其實相當於[入站,入境]
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg)
throws Exception {
// 獲取channel
Channel channel = ctx.channel();
if (msg 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());
// 把響應刷到客戶端
ctx.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("channeld讀取完畢。。。");
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);
}
}
## 五、啟動主類 進行測試
后台完整輸出
六、客戶端線程模型
相比於服務端,客戶端的線程模型簡單一些,它的工作原理如下:
代碼如下
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group) // 注冊線程池
.channel(NioSocketChannel.class) // 使用NioSocketChannel來作為連接用的channel類
.remoteAddress(new InetSocketAddress(this.host, this.port)) // 綁定連接端口和host信息
.handler(new ChannelInitializer<SocketChannel>() { // 綁定連接初始化器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//這里放入自定義助手類
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture cf = b.connect().sync(); // 異步連接服務器
cf.channel().closeFuture().sync(); // 異步等待關閉連接channel
} finally {
group.shutdownGracefully().sync(); // 釋放線程池資源
}
}
客戶端的開發步驟和服務端都差不多:
(1)、 初始化用於連接及I/O工作的"線程池";
(2)、 初始化`Bootstrap`實例, 此實例是netty客戶端應用開發的入口;
(3)、 通過Bootstrap的group方法,設置(1)中初始化的"線程池";
(4)、 指定通道channel的類型,由於是客戶端,故而是`NioSocketChannel`;
(5)、 設置SocketChannel的選項;
(6)、 設置SocketChannel的處理器, 其內部是實際業務開發的"主戰場";
(7)、 連接指定的服務地址;
相比於服務端很明顯的區別在於
(1),客戶端只需要創建一個 EventLoopGroup,因為它不需要獨立的線程去監聽客戶端連接,也沒必要通過一個單獨的客戶端線程去連接服務端。Netty 是異步事件驅動的 NIO 框架,它的連接和所有 IO 操作都是異步的,因此不需要創建單獨的連接線程。
(2)服務端引導類ServerBootstrap
,而客戶端引導是Bootstrap
進行開發的,不過它們都需要通過group屬性指定EventLoopGroup, 因為是開發NIO程序,所以我們選擇NioEventLoopGroup。
如果一個人充滿快樂,正面的思想,那么好的人事物就會和他共鳴,而且被他吸引過來。同樣,一個人老帶悲傷,倒霉的事情也會跟過來。
——在自己心情低落的時候,告誡自己不要把負能量帶給別人。(大校11)