概述
前面基本分析了一下Netty框架学习的一些前置概念和基础,其实netty都为我们封装好了,现在我们来写一个示例来加深印象
开始实践
项目在之前的https://gitee.com/kaixinshow/java-nionetty-learning基础上创建一个netty包
1.创建一个服务器端:HttpServer
/**
* @ClassName HttpServer
* @Description //HttpServer
* @Author singleZhang
* @Email [email protected]
* @Date 2021/2/25 0025 上午 11:40
**/
public class HttpServer {
public static void main(String[] args) {
// 构造两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
//服务端启动辅助类
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerInitializer());
//启动
ChannelFuture channelFuture = bootstrap.bind(8088).sync();
//等待服务端口关闭
channelFuture.channel().closeFuture().sync();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
2.创建自定义通道初始化器:HttpServerInitializer 继承ChannelInitializer
/**
* @ClassName HttpServerInitializer
* @Description //HttpServerInitializer
* @Author singleZhang
* @Email [email protected]
* @Date 2021/2/25 0025 上午 11:44
**/
public class HttpServerInitializer extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline ch = socketChannel.pipeline();
//HttpServerCodec可以被HttpResponseEncoder、HttpRequestDecoder两个替代
ch.addLast("httpServerCodec",new HttpServerCodec())
.addLast("httpServerHandler",new HttpServerHandler());
}
}
3.创建自定义的 ChannelHandler 组件,处理自定义的业务逻辑:HttpServerHandler
/**
* @ClassName HttpServerHandler
* @Description //HttpServerHandler
* @Author singleZhang
* @Email [email protected]
* @Date 2021/2/25 0025 上午 11:49
**/
public class HttpServerHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if(msg instanceof HttpRequest){
HttpRequest request = (HttpRequest) msg;
System.out.println(request.method().name());
System.out.println(request.uri());
}
if(msg instanceof HttpContent){
HttpContent content = (HttpContent) msg;
ByteBuf buf = content.content();
System.out.println(buf.toString(CharsetUtil.UTF_8));
ByteBuf resBuf = Unpooled.copiedBuffer("hello,server test!",CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,resBuf);
response.headers().add(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().add(HttpHeaderNames.CONTENT_LENGTH,resBuf.readableBytes());
ctx.writeAndFlush(response);
}
}
}
这样就完成了一个简单的服务器端程序,用来处理http请求/响应,启动程序并用postman测试一下:
- 写一个客户端:HttpClient
/**
* @ClassName HttpClient
* @Description //HttpClient
* @Author singleZhang
* @Email [email protected]
* @Date 2021/2/25 0025 下午 2:52
**/
public class HttpClient {
public static void main(String[] args) {
String host = "localhost";
int port = 8088;
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline cp = channel.pipeline();
cp.addLast(new HttpClientCodec())
//HttpObjectAggregator将请求转为FullHttpRequest
.addLast("aggregator", new HttpObjectAggregator(65536))
.addLast(new HttpClientHandler());
}
});
// 启动客户端.
ChannelFuture f = bootstrap.connect(host, port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
5.创建自定义的 ChannelHandler 组件,处理自定义的业务逻辑:HttpClientHandler
/**
* @ClassName HttpClientHandler
* @Description //HttpClientHandler
* @Author singleZhang
* @Email [email protected]
* @Date 2021/2/25 0025 下午 3:00
**/
public class HttpClientHandler extends SimpleChannelInboundHandler {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
URI uri = new URI("http://localhost:8080");
String msg ="hi,client test!";
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,HttpMethod.GET,uri.toASCIIString(),
Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));
ctx.channel().writeAndFlush(request);
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpResponse msg) throws Exception {
FullHttpResponse response = msg;
response.headers().get(HttpHeaderNames.CONTENT_TYPE);
ByteBuf buf = response.content();
System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
}
}
这样启动服务器端和客户端,显示的结果如下:
总结
在这个示例程序里,我们使用了Netty中的一些类,如:NioEventLoopGroup、ServerBootstrap、Bootstrap、Channel、NioSocketChannel、ChannelInitializer、ChannelPipeline、SimpleChannelInboundHandler等等。
- NioEventLoopGroup 实际上就是个线程池,一个 EventLoopGroup 包含一个或者多个 EventLoop
上边的bossGroup 线程池里其实就只有一个线程来处理连接,workerGroup 中的线程是 CPU 核心数乘以2。
一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
所有有 EnventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
一个 Channel 在它的生命周期内只注册于一个 EventLoop
每一个 EventLoop 负责处理一个或多个 Channel。 - Channel 是Netty 中的一个通道接口,Netty 实现的客户端 NIO 套接字通道是 NioSocketChannel,提供的服务器端 NIO 套接字通道是 NioServerSocketChannel。当服务端和客户端建立一个新的连接时, 一个新的 Channel 将被创建,同时它会被自动地分配到它专属的 ChannelPipeline。
- ChannelPipeline,它是一个拦截流经 Channel 的入站和出站事件的 ChannelHandler 实例链,并定义了用于在该链上传播入站和出站事件流的 API。那么就很容易看出这些 ChannelHandler 之间的交互是组成一个应用程序数据和事件处理逻辑的核心。
- ChannelHandler ,有ChannelInBoundHandler (入站处理器)、 ChannelOutboundHandler(出站处理器)之分
如果一个入站 I/O 事件被触发,这个事件会从第一个开始依次通过 ChannelPipeline中的 ChannelInBoundHandler,先添加的先执行;
如果是一个出站 I/O 事件,则会从最后一个开始依次通过 ChannelPipeline 中的 ChannelOutboundHandler,后添加的先执行,然后通过调用在 ChannelHandlerContext 中定义的事件传播方法传递给最近的 ChannelHandler。
如果往ChannelPipeline添了一串ChannelInBoundHandler和ChannelOutboundHandler,如果是一个 “ 入站 ” 事件,它开始于头部,按顺序进入ChannelInBoundHandler若是一个 “ 出站 ” 事件,则开始于尾部,倒序执行进入ChannelOutboundHandler。