每一个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{
@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{
@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”被打印出来。
将以上的CustomHandler.java类里面的channel对应的方法一一实现。
/**
* 创建人:taofut
* 创建时间:2018-12-19 20:48
* 描述:创建自定义助手类
*/
//SimpleChannelInboundHandler:对于请求来讲,相当于入站、入境
public class CustomHandler extends SimpleChannelInboundHandler{
@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移除…】
【助手类移除…】