钻石恒久远,helloWorld 永流传。作为第一个netty的入门程序,也来写一个基于netty实现的helloWorld,直接上代码
服务器代码
public class HttpServer {
public static void main(String[] args) throws InterruptedException {
// acceptor 使用的线程池 它只处理连接请求 不做具体的工作 所以叫boss
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 真正执行业务的线程池,针对于boss 它就是worker
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 定义一个服务器启动
ServerBootstrap bootstrap = new ServerBootstrap()
// 给服务指定工作线程池 此处采用构建者模式
.group(bossGroup, workerGroup)
// 指定使用的通道类型尾nio的服务器通道
.channel(NioServerSocketChannel.class)
// 指定一个通道处理器,也就是第一次请求过来了,需要建立长连接的时候做干些什么
.childHandler(new HttpServerChannelInitializer());
// 异步的去绑定一个端口号
ChannelFuture channelFuture = bootstrap.bind(18888).sync();
// 异步的关闭通道
channelFuture .channel().closeFuture().sync();
}finally {
// 使用netty提供的api 关闭线程池 Gracefully-> 优雅的 温柔的
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务器通道初始化器
public class HttpServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 获取管道 pipeline 是一个很重要的概念,在tomcat的容器部分同样使用了pipeline,它体现了责任分发的思想
ChannelPipeline pipeline = ch.pipeline();
// 添加一个针对于http的通道处理器
pipeline.addLast("httpServerCodec", new HttpServerCodec());
// 添加一个自定义的处理器 针对于每一个通道(连接) 都应该指定一个新的处理器,这个处理器不能是单例的 因为netty里面很多并发支持
pipeline.addLast("httpServerChannelHandler", new HttpServerChannelHandler());
}
}
通道处理器
public class HttpServerChannelHandler extends SimpleChannelInboundHandler<HttpObject> {
private static final Logger logger = LoggerFactory.getLogger(HttpServerChannelHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// 获取请求的消息
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
// 打印当前请求的url和方法
logger.info("url:{},method:{}", request.uri(), request.method());
// 构建一个netty独有的字节流对象
ByteBuf content = Unpooled.copiedBuffer("hello world!", CharsetUtil.UTF_8);
// 构建一个响应对象
HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
// 设置响应内容的长度
response.headers().add(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
// 设置内容类型
response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
// write 只是将数据写到了缓冲区,所以需要flush一下
ctx.writeAndFlush(response);
}
}
}
编写完成后直接启动HttpServer中的main方法 ,然后打开浏览器 输入:http://localhost:18888即可看到
效果
总结
概念 | 解释 |
---|---|
initializer | 初始化器,客户端成功连接到服务端的时候会触发初始化器中的初始化回调方法 |
channel | 通道(连接),与io流的区别是io流是单向,但是通道是双向的,也就是通道既可以接收,又可以发送 |
channelHandler | 通道处理器,服务器/客户端收到消息的时候作出相应的处理 |
pipeline 管道 | 用来装载channelHandler的容器 |
inbound | 入站,处理请求数据 |
outbound | 出站,处理响应数据 |
bossGroup | 服务器端处理请求连接的线程池,又叫 parentGroup |
workerGroup | 服务器端真正处理请求的线程池, 又叫 childGroup |
为什么ServerBootstrap要使用2个线程池
因为bossGroup 负责处理请求的连接,workerGroup 负责处理业务,如果使用一个线程池,当并发量高的时候会出现不能够及时处理连接的情况
ServerBootstrap中的childHandler 和 handler方法有什么区别
childHandler 提供给workerGroup中的线程使用
handler 提供给bossGroup中的线程使用,
sync()
标志该动作执行完毕之后再继续执行下面的代码,如果动作尚未执行完毕,则阻塞当前线程
> bootstrap.bind(18888).sync() // 当操作系统的端口被成功绑定 才会继续执行下面代码,否则阻塞
> channelFuture .channel().closeFuture().sync() // 当连接通道被成功关闭的时候才会继续往下执行
为什么netty 不支持Servlet规范
虽然netty可以充当http服务器,但是更多的人更倾向于使用tomcat,因为netty本身没有提供任何关于servlet规范的支持,netty的重心一直放在更底层,所以如果要使用netty实现http服务器的功能,需要自己根据需求开发