概述

前面基本分析了一下Netty框架学习的一些前置概念和基础,其实netty都为我们封装好了,现在我们来写一个示例来加深印象

开始实践

1.创建一个服务器端:HttpServer

/**
* @ClassName HttpServer
* @Description //HttpServer
* @Author singleZhang
* @Email 405780096@qq.com
* @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 405780096@qq.com
* @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 405780096@qq.com
* @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测试一下:

postman

server

写一个客户端:HttpClient

/**
* @ClassName HttpClient
* @Description //HttpClient
* @Author singleZhang
* @Email 405780096@qq.com
* @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 405780096@qq.com
* @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));
}
}

这样启动服务器端和客户端,显示的结果如下:

server

client

总结

在这个示例程序里,我们使用了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。