概述
概述
Netty本身的是什么,它是什么特性等问题本文暂不做解释,日后学习过程中,切身体会了之后,再来讲一讲。
Netty能干什么,主要如下:
- 能作为一个RPC的通讯框架,基于socket方式实现服务间的远程调用
- 能作为开发长连接业务场景的基础(websocket),实现客户端和服务端的长连接通讯
- 可作为http的服务器,类似于tomcat等server容器,只不过它不是基础servlet的,它并没有实现相关servlet的能力,有自己的一套处理方式
示例
对于Netty初学者来说(像我这种人QAQ),Netty的上手是很难的一件事情,官方的文档并不是很友好,网络上的讲解也不是很清楚,所以很直观的就是写一个接收到请求并相应demo来感受一下Netty在处理请求时候的大致流程。以下是Netty的“Hello word”
由于使用的是Gradle构建的项目,引入如下依赖
plugins {
id 'java'
}
group 'com.leolee'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
implementation("io.netty:netty-all:4.1.51.Final")
}
注意Netty的包有很多,我们要直接引入Netty-all就可以了,everything in one肯定没错
需要创建如下三个测试类
- TestServer
- TestServerInitializer
- TestHttpServerHandler
package com.leolee.netty.firstExample;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @ClassName TestServer
* @Description: 服务端,简单的实现客户端调用服务端的时候返回一个字符串的场景
* @Author LeoLee
* @Date 2020/8/22
* @Version V1.0
**/
public class TestServer {
public static void main(String[] args) throws InterruptedException {
//定义线程组 EventLoopGroup为死循环
//boss线程组一直在接收客户端发起的请求,但是不对请求做处理,boss会将接收到的请i交给worker线程组来处理
//实际可以用一个线程组来做客户端的请求接收和处理两件事,但是不推荐
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//启动类定义
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TestServerInitializer());//子处理器,自定义处理器
//绑定监听端口
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
//定义关闭监听
channelFuture.channel().closeFuture().sync();
} finally {
//Netty提供的优雅关闭
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.leolee.netty.firstExample;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
/**
* @ClassName TestServerInitializer
* @Description: 初始化管道并绑定处理器
* @Author LeoLee
* @Date 2020/8/22
* @Version V1.0
**/
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//声明管道
ChannelPipeline pipeline = ch.pipeline();
//在管道最后增加一个处理器:HttpServerCodec,对请求进行编解码用
pipeline.addLast("HttpServerCodec", new HttpServerCodec());
//绑定自定义的处理提
pipeline.addLast("TestHttpServerHandler", new TestHttpServerHandler());
}
}
package com.leolee.netty.firstExample;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
/**
* @ClassName TestHttpServerHandler
* @Description: 自定义请求处理器
* @Author LeoLee
* @Date 2020/8/22
* @Version V1.0
**/
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
/**
* 功能描述: <br> 读取客户端请求并返回相应
* @Param: [ctx, msg]
* @Return: void
* @Author: LeoLee
* @Date: 2020/8/22 16:03
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//构造返回内容
ByteBuf content = Unpooled.copiedBuffer("Hello word", CharsetUtil.UTF_8);
//构造相应包体
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);
}
}
由于ServerBootstrap启动类实际上是持续在运行的,所以我们运行main方法后控制台如下
请求http://localhost:8899/
重要组件如下:
- EventLoopGroup
- ServerBootstrap
- 内置处理或自定义处理器
所以总结一下执行流程如下:
- 创建并启动ServerBootstrap服务器
- ServerBootstrap服务器接收两个线程组,一个负责接收请求,一个负责处理请求
- ServerBootstrap要绑定子处理器,可添加内置处理和自定义处理器来处理业务请求
优化
有些人可能看到,浏览器请求发出了两次请求,多的是一次请求是去获取网页的icon图标了,这时候浏览器对服务端监听的8899端口进行了两次请求,因为服务端只监听了8899,所以多出的这次icon请求我们也做了业务处理
所以对自定义处理器改造,如下:
package com.leolee.netty.firstExample;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.net.URI;
/**
* @ClassName TestHttpServerHandler
* @Description: 自定义请求处理器
* @Author LeoLee
* @Date 2020/8/22
* @Version V1.0
**/
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
/**
* 功能描述: <br> 读取客户端请求并返回相应
* @Param: [ctx, msg]
* @Return: void
* @Author: LeoLee
* @Date: 2020/8/22 16:03
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
System.out.println("这是一次" + request.method().name() + "请求");
URI uri = new URI(request.uri());
System.out.println(uri.getPath());
if (uri.getPath().equals("/favicon.ico")) {
System.out.println("favicon.ico请求");
return;
}
//构造返回内容
ByteBuf content = Unpooled.copiedBuffer("Hello word", CharsetUtil.UTF_8);
//构造相应包体
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);
}
}
}
过滤掉浏览器的网页icon请求的处理
回调过程
在自定义处理器 TestHttpSeverHandler 中,其继承了 SimpleChannelInboundHandler 抽象类,SimpleChannelInboundHandler 又继承了 ChannelInboundHandlerAdapter 。我们先忽略SimpleChannelInboundHandler ,来看看 ChannelInboundHandlerAdapter 做了什么事情。
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel;
import io.netty.channel.ChannelHandlerMask.Skip;
/**
* Abstract base class for {@link ChannelInboundHandler} implementations which provide
* implementations of all of their methods.
*
* <p>
* This implementation just forward the operation to the next {@link ChannelHandler} in the
* {@link ChannelPipeline}. Sub-classes may override a method implementation to change this.
* </p>
* <p>
* Be aware that messages are not released after the {@link #channelRead(ChannelHandlerContext, Object)}
* method returns automatically. If you are looking for a {@link ChannelInboundHandler} implementation that
* releases the received messages automatically, please see {@link SimpleChannelInboundHandler}.
* </p>
*/
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
/**
* Calls {@link ChannelHandlerContext#fireChannelRegistered()} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
/**
* Calls {@link ChannelHandlerContext#fireChannelUnregistered()} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelUnregistered();
}
/**
* Calls {@link ChannelHandlerContext#fireChannelActive()} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
}
/**
* Calls {@link ChannelHandlerContext#fireChannelInactive()} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelInactive();
}
/**
* Calls {@link ChannelHandlerContext#fireChannelRead(Object)} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
/**
* Calls {@link ChannelHandlerContext#fireChannelReadComplete()} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelReadComplete();
}
/**
* Calls {@link ChannelHandlerContext#fireUserEventTriggered(Object)} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
/**
* Calls {@link ChannelHandlerContext#fireChannelWritabilityChanged()} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelWritabilityChanged();
}
/**
* Calls {@link ChannelHandlerContext#fireExceptionCaught(Throwable)} to forward
* to the next {@link ChannelHandler} in the {@link ChannelPipeline}.
*
* Sub-classes may override this method to change behavior.
*/
@Skip
@Override
@SuppressWarnings("deprecation")
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.fireExceptionCaught(cause);
}
}
该类中有很多回调方法,这些方法都是与事件相关,也就是说当某些时间发生的时候这些方法将会被调用,启动项目之后,用curl来请求服务端会出现以下输出
handler added
channel registered
channel active
这是一次GET请求
/
channel inactive
channel unregistered
这其实是可以理解的,先添加了管道处理器,再注册管道,管道处于活跃状态,处理请求,管道失活,管道注销,但是如果你用浏览器去请求的话结果如下:
handler added
handler added
channel registered
channel registered
channel active
channel active
这是一次GET请求
/
这是一次GET请求
/favicon.ico
favicon.ico请求
由于是两次请求的关系,上面的输出也输出并处理了两次请求,但唯独没有出现两次channel inactive、channel unregistered,当你再次用浏览器请求服务端的时候又会出现如下输出
这是一次GET请求
/
channel inactive
channel unregistered
这是一次GET请求
/favicon.ico
favicon.ico请求
也就是上一次请求的channel inactive、channel unregistered出现了,但是没有handler added、channel registered、channel active,这证明连续的请求是不需要重复这三个过程的,同时也说明浏览器请求和curl请求机制的不一样,curl请求每次都是执行一个简单的、完整的请求过程,当curl命令请求完成后它也会对服务端进行相应的通知,而浏览器并不会,其只在一定时间过后或者是关闭浏览器之后进行这些通知操作。
在整个服务端处理请求的过程中,其实还是会根据请求的协议类型(http1.0 http1.1)来做出区别,当请求时http1.1的时候我们自己可以通过去判断客户端设置的keeplive时间,在这段时间内如果没有客户端的再次请求,则在服务端进行关闭。
/**
* 功能描述: <br> 读取客户端请求并返回相应
* @Param: [ctx, msg]
* @Return: void
* @Author: LeoLee
* @Date: 2020/8/22 16:03
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
System.out.println("这是一次" + request.method().name() + "请求");
URI uri = new URI(request.uri());
System.out.println(uri.getPath());
if (uri.getPath().equals("/favicon.ico")) {
System.out.println("favicon.ico请求");
return;
}
//构造返回内容
ByteBuf content = Unpooled.copiedBuffer("Hello word", CharsetUtil.UTF_8);
//构造相应包体
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);
//可以手动关闭与客户端建立的联系
if (true) { //在这里判断请求时1.0还是1.1,已经是否超过keeplive时间
ctx.channel().close();
}
}
}
需要代码的来这里拿嗷:demo项目git地址
最后
以上就是独特薯片为你收集整理的Netty学习(1):Http服务器建立以及执行流程分析和重要组件介绍概述示例优化回调过程的全部内容,希望文章能够帮你解决Netty学习(1):Http服务器建立以及执行流程分析和重要组件介绍概述示例优化回调过程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复