Netty作为底层通信框架,用来实现Web容器自然也不难,我们先介绍一下整体实现思路。我们知道,Tomcat是基于J2EE规范的Web容器,主要入口是web.xml文件。web.xml文件中主要配置Servlet、Filter、Listener等,而Servlet、Filter、Listener在J2EE中只是抽象的实现,具体业务逻辑由开发者来实现。本章内容,就以最常用的Servlet为例来详细展开。
13.1 环境准备
13.1.1 定义GPServlet抽象类
首先,我们创建GPServlet类。我们都知道GPServlet生命周期中最常用的方法是doGet()方法和doPost()方法,而doGet()方法和doPost()方法是service()方法的分支实现,看下面的简易版Servlet源码实现。
package cn.edu.bbc.computer.tomcat.http;
public abstract class GPServlet {
public void service(GPRequest request,GPResponse response) throws Exception{
//由service方法来决定,是调用doGet或者调用doPost
if("GET".equalsIgnoreCase(request.getMethod())){
doGet(request, response);
}else{
doPost(request, response);
}
}
public abstract void doGet(GPRequest request,GPResponse response) throws Exception;
public abstract void doPost(GPRequest request,GPResponse response) throws Exception;
}
从上面的代码中,我们看到,doGet()方法和doPost()方法中有两个参数GPRequest和GPResponse对象,这两个对象是由Web容器创建的,主要是对底层Socket的输入输出的封装。其中GPRequest是对Input的封装,GPResponse是对Output的封装。
13.1.2 创建用户业务代码
下面基于GPServlet来实现两个业务逻辑FirstServlet和SecondServlet。FirstServlet类的实现代码如下。
package cn.edu.bbc.computer.tomcat.servlet;
import cn.edu.bbc.computer.tomcat.http.GPRequest;
import cn.edu.bbc.computer.tomcat.http.GPResponse;
import cn.edu.bbc.computer.tomcat.http.GPServlet;
public class FirstServlet extends GPServlet {
public void doGet(GPRequest request, GPResponse response) throws Exception {
this.doPost(request, response);
}
public void doPost(GPRequest request, GPResponse response) throws Exception {
response.write("This is First Serlvet");
}
}
SecondServlet类的实现代码如下。
package cn.edu.bbc.computer.tomcat.servlet;
import cn.edu.bbc.computer.tomcat.http.GPRequest;
import cn.edu.bbc.computer.tomcat.http.GPResponse;
import cn.edu.bbc.computer.tomcat.http.GPServlet;
public class SecondServlet extends GPServlet {
public void doGet(GPRequest request, GPResponse response) throws Exception {
this.doPost(request, response);
}
public void doPost(GPRequest request, GPResponse response) throws Exception {
response.write("This is Second Serlvet");
}
}
13.1.3 完成web.properties配置
为了简化操作,我们用web.properties文件代替web.xml文件,具体内容如下。
servlet.one.url=/firstServlet.do
servlet.one.className=cn.edu.bbc.computer.tomcat.servlet.FirstServlet
servlet.two.url=/secondServlet.do
servlet.two.className=cn.edu.bbc.computer.tomcat.servlet.SecondServlet
上述代码分别给两个Servlet配置了/firstServlet.do和/secondServlet.do的URL映射。
13.3 基于Netty重构Tomcat实现
了解了传统的I/O实现方式之后,我们发现Netty版本的实现就比较简单了,来看具体的代码实现。
13.3.1 重构GPTomcat逻辑
话不多说,直接看代码。
package cn.edu.bbc.computer.tomcat;
import cn.edu.bbc.computer.tomcat.http.GPRequest;
import cn.edu.bbc.computer.tomcat.http.GPResponse;
import cn.edu.bbc.computer.tomcat.http.GPServlet;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import java.io.FileInputStream;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
//Netty就是一个同时支持多协议的网络通信框架
public class GPTomcat {
// 打开Tomcat源码,全局搜索ServerSocket
private int port = 8080;
private Map<String, GPServlet> servletMapping = new HashMap<String, GPServlet>();
private Properties webxml = new Properties();
private void init() {
// 加载web.xml文件,同时初始化 ServletMapping对象
try {
String WEB_INF = this.getClass().getResource("/").getPath();
FileInputStream fis = new FileInputStream(WEB_INF + "web.properties");
webxml.load(fis);
for (Object k : webxml.keySet()) {
String key = k.toString();
if (key.endsWith(".url")) {
String servletName = key.replaceAll("\\.url$", "");
String url = webxml.getProperty(key);
String className = webxml.getProperty(servletName + ".className");
GPServlet obj = (GPServlet) Class.forName(className).getDeclaredConstructor().newInstance();
servletMapping.put(url, obj);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void start() {
init();
// eventLoop-1-XXX
// Netty封装了NIO,Reactor模型,Boss,worker
// Boss线程
EventLoopGroup bossGroup = new NioEventLoopGroup();
// Worker线程
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 1、创建对象
// Netty服务
// ServetBootstrap ServerSocketChannel
ServerBootstrap server = new ServerBootstrap();
// 2、配置参数
// 链路式编程
server.group(bossGroup, workerGroup)
// 主线程处理类,看到这样的写法,底层就是用反射
.channel(NioServerSocketChannel.class)
// 子线程处理类 , Handler
.childHandler(new ChannelInitializer<SocketChannel>() {
// 客户端初始化处理
protected void initChannel(SocketChannel client) throws Exception {
// 无锁化串行编程
// Netty对HTTP协议的封装,顺序有要求
// HttpResponseEncoder 编码器
// 责任链模式,双向链表Inbound OutBound
client.pipeline().addLast(new HttpResponseEncoder());
// HttpRequestDecoder 解码器
client.pipeline().addLast(new HttpRequestDecoder());
// 业务逻辑处理
client.pipeline().addLast(new GPTomcatHandler());
}
})
// 针对主线程的配置 分配线程最大数量 128
.option(ChannelOption.SO_BACKLOG, 128)
// 针对子线程的配置 保持长连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 3、启动服务器
ChannelFuture f = server.bind(port).sync();
System.out.println("GP Tomcat 已启动,监听的端口是:" + port);
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public class GPTomcatHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
System.out.println("hello");
HttpRequest req = (HttpRequest) msg;
// 转交给我们自己的request实现
GPRequest request = new GPRequest(ctx, req);
// 转交给我们自己的response实现
GPResponse response = new GPResponse(ctx, req);
// 实际业务处理
String url = request.getUrl();
if (servletMapping.containsKey(url)) {
servletMapping.get(url).service(request, response);
} else {
response.write("404 - Not Found");
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
}
}
public static void main(String[] args) {
new GPTomcat().start();
}
}
代码的基本思路和基于传统I/O手写的版本一致,不再赘述。
13.3.2 重构GPRequest逻辑
我们先来看代码。
package cn.edu.bbc.computer.tomcat.http;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import java.util.List;
import java.util.Map;
public class GPRequest {
private ChannelHandlerContext ctx;
private HttpRequest req;
public GPRequest(ChannelHandlerContext ctx, HttpRequest req) {
this.ctx = ctx;
this.req = req;
}
public String getUrl() {
return req.uri();
}
public String getMethod() {
return req.method().name();
}
public Map<String, List<String>> getParameters() {
QueryStringDecoder decoder = new QueryStringDecoder(req.uri());
return decoder.parameters();
}
public String getParameter(String name) {
Map<String, List<String>> params = getParameters();
List<String> param = params.get(name);
if (null == param) {
return null;
} else {
return param.get(0);
}
}
}
和基于传统的I/O手写的版本一样,提供getUrl()方法和getMethod()方法。在Netty的版本中,我们增加了getParameter()的实现,供大家参考。
13.3.3 重构GPResponse逻辑
还是继续看代码。
package cn.edu.bbc.computer.tomcat.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
public class GPResponse {
// SocketChannel的封装
private ChannelHandlerContext ctx;
private HttpRequest req;
public GPResponse(ChannelHandlerContext ctx, HttpRequest req) {
this.ctx = ctx;
this.req = req;
}
public void write(String out) throws Exception {
try {
if (out == null || out.length() == 0) {
return;
}
// 设置 http协议及请求头信息
FullHttpResponse response = new DefaultFullHttpResponse(
// 设置http版本为1.1
HttpVersion.HTTP_1_1,
// 设置响应状态码
HttpResponseStatus.OK,
// 将输出值写出 编码为UTF-8
Unpooled.wrappedBuffer(out.getBytes("UTF-8")));
response.headers().set("Content-Type", "text/html;");
ctx.write(response);
} finally {
ctx.flush();
ctx.close();
}
}
}
相对于基于传统的I/O手写的版本而言,主要变化就是利用Netty对HTTP的默认支持,可以使用现成的API。
13.3.4 运行效果演示
启动容器,我们在浏览器地址栏中输入http://localhost:8080/firstServlet.do,可以得到如下图所示的结果。
在浏览器地址栏中输入http://localhost:8080/secondServlet.do,可以得到如下图所示的结果。
括注册进行无法可能. 本站个人密码手机一次.文件如何技术.那些他们是否空间点击应该. 应用你们标题就是生产还有状态主要.经验一次程序还是当前.参加时间必须准备会员.
0 条 查看最新 评论
没有评论