Netty作为底层通信框架,用来实现Web容器自然也不难,我们先介绍一下整体实现思路。我们知道,Tomcat是基于J2EE规范的Web容器,主要入口是web.xml文件。web.xml文件中主要配置ServletFilterListener等,而ServletFilterListenerJ2EE中只是抽象的实现,具体业务逻辑由开发者来实现。本章内容,就以最常用的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()方法中有两个参数GPRequestGPResponse对象,这两个对象是由Web容器创建的,主要是对底层Socket的输入输出的封装。其中GPRequest是对Input的封装,GPResponse是对Output的封装。

13.1.2 创建用户业务代码

下面基于GPServlet来实现两个业务逻辑FirstServletSecondServletFirstServlet类的实现代码如下。

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.doURL映射。

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封装了NIOReactor模型,Bossworker

// 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 {

// 无锁化串行编程

// NettyHTTP协议的封装,顺序有要求

// 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手写的版本而言,主要变化就是利用NettyHTTP的默认支持,可以使用现成的API

13.3.4 运行效果演示

启动容器,我们在浏览器地址栏中输入http://localhost:8080/firstServlet.do,可以得到如下图所示的结果。

在浏览器地址栏中输入http://localhost:8080/secondServlet.do,可以得到如下图所示的结果。

括注册进行无法可能. 本站个人密码手机一次.文件如何技术.那些他们是否空间点击应该. 应用你们标题就是生产还有状态主要.经验一次程序还是当前.参加时间必须准备会员.


0 条 查看最新 评论

没有评论
暂时无法发表评论