上一篇写了,基于netty实现的rpc的微框架,其中详细介绍netty的原理及组件,这篇就不过多介绍

这篇实现基于netty的web框架,你说netty强不强,文中有不对的地方,欢迎大牛指正

先普及几个知识点

@sharable

标注一个channel handler可以被多个channel安全地共享。
ChannelHandlerAdapter还提供了实用方法isSharable()。如果其对应的实现被标注为Sharable,那么这个方法将返回true,
表示它可以被添加到多个ChannelPipeline中。 因为一个ChannelHandler可以从属于多个ChannelPipeline,所以它也可以绑定到多个ChannelHandlerContext实例。
用于这种用法的ChannelHandler必须要使用@Sharable注解标注;否则,试图将它添加到多个ChannelPipeline时将会触发异常。
显而易见,为了安全地被用于多个并发的Channel(即连接),这样的ChannelHandler必须是线程安全的。

AtomicInteger:这个类的存在是为了满足在高并发的情况下,原生的整形数值自增线程不安全的问题,在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。AtomicInteger为什么能够达到多而不乱,处理高并发应付自如呢?

这是由硬件提供原子操作指令实现的,这里面用到了一种并发技术:CAS。在非激烈竞争的情况下,开销更小,速度更快

TimeUnit: 

TimeUnit是Java.util.concurrent包下面的一个类。它提供了两大功能:

1)提供了可读性更好的线程暂停操作,通常用来替换Thread.sleep();

2)提供了便捷方法用于把时间转换成不同单位,如把秒转换成毫秒;

TimeUnit.MINUTES.sleep(4);  // sleeping for 4 minutes

Thread.sleep(4*60*1000);

项目的目录结构

上代码,分享一些关键的代码,后续的giuhub上的demo的注释很详细

//Netty 事件回调类
@Sharable
public class MessageCollector extends ChannelInboundHandlerAdapter {
private final static Logger LOG = LoggerFactory.getLogger(MessageCollector.class);
//业务线程池
private ThreadPoolExecutor[] executors;
private RequestDispatch requestDispatch;
//业务队列最大值
private int requestsMaxInflight=1000; public MessageCollector(int workerThreads,RequestDispatch dispatch){
//给业务线程命名
ThreadFactory factory =new ThreadFactory() {
AtomicInteger seq=new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread thread =new Thread(r);
thread.setName("http-"+seq.getAndIncrement());
return thread;
}
};
this.executors=new ThreadPoolExecutor[workerThreads];
for(int i=0;i<workerThreads;i++){
ArrayBlockingQueue queue=new ArrayBlockingQueue<Runnable>(requestsMaxInflight);
////闲置时间超过30秒的线程就自动销毁
this.executors[i]=new ThreadPoolExecutor(1,1,
30, TimeUnit.SECONDS, queue,factory,new CallerRunsPolicy());
} this.requestDispatch=dispatch;
} public void closeGracefully(){
//优雅一点关闭,先通知,再等待,最后强制关闭
for (int i=0;i<executors.length;i++){
ThreadPoolExecutor executor=executors[i];
try {
executor.awaitTermination(10,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdownNow();
}
} @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//客户端来了一个新的连接
LOG.info("connection comes {}",ctx.channel().remoteAddress());
} @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//客户端走了一个
LOG.info("connection leaves {}",ctx.channel().remoteAddress());
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest){
FullHttpRequest req= (FullHttpRequest) msg;
CRC32 crc32=new CRC32();
crc32.update(ctx.hashCode());
int idx =(int) (crc32.getValue()%executors.length);
//用业务线程处理消息
this.executors[idx].execute(() ->{
requestDispatch.dispatch(ctx,req);
});
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//此处可能因为客户机器突发重启
//也可能客户端连接时间超时,后面的REadTimeoutHandle抛出异常
//也可能消息协议错误,序列化异常
ctx.close();
}
}
HttpServer
public class HttpServer {
private final static Logger LOG= LoggerFactory.getLogger(HttpServer.class);
private String ip;
private int port; //端口
private int ioThreads; //IO线程数,用于处理套接字读写,由Netty内部管理
private int workerThreads; //业务线程数,专门处理http请求,由我们本省框架管理
private RequestDispatch requestDispatch;//请求配发器对象 public HttpServer() {
} public HttpServer(String ip, int port, int ioThreads,
int workerThreads, RequestDispatch requestDispatch) {
this.ip = ip;
this.port = port;
this.ioThreads = ioThreads;
this.workerThreads = workerThreads;
this.requestDispatch = requestDispatch;
}
//用于服务端,使用一个ServerChannel接收客户端的连接,
// 并创建对应的子Channel
private ServerBootstrap bootstrap;
//包含多个EventLoop
private EventLoopGroup group;
//代表一个Socket连接
private Channel serverChannel;
//
private MessageCollector collector; public void start(){
bootstrap=new ServerBootstrap();
group=new NioEventLoopGroup(ioThreads);
bootstrap.group(group);
collector=new MessageCollector(workerThreads,requestDispatch);
bootstrap.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline=socketChannel.pipeline();
//如果客户端60秒没有任何请求,就关闭客户端连接
pipeline.addLast(new ReadTimeoutHandler(10));
//客户端和服务器简单的编解码器:HttpClientCodec和HttpServerCodec。
//ChannelPipelien中有解码器和编码器(或编解码器)后就可以操作不同的HttpObject消息了;但是HTTP请求和响应可以有很多消息数据,
// 你需要处理不同的部分,可能也需要聚合这些消息数据
pipeline.addLast(new HttpServerCodec());
//通过HttpObjectAggregator,Netty可以聚合HTTP消息,
// 使用FullHttpResponse和FullHttpRequest到ChannelPipeline中的下一个ChannelHandler,这就消除了断裂消息,保证了消息的完整
pipeline.addLast(new HttpObjectAggregator(1 << 30)); // max_size = 1g
//允许通过处理ChunkedInput来写大的数据块
pipeline.addLast(new ChunkedWriteHandler());
//将业务处理器放到最后
pipeline.addLast(collector);
}
});
} public void stop() {
// 先关闭服务端套件字
serverChannel.close();
// 再斩断消息来源,停止io线程池
group.shutdownGracefully();
// 最后停止业务线程
collector.closeGracefully();
} }
RequestDispatcherImpl 是请求派发器,用于将收到的HTTP请求对象扔给响应的RequestHandler进行处理。
public class RequestDispatcherImpl implements RequestDispatch {
private final static Logger LOG = LoggerFactory.getLogger(RequestDispatcherImpl.class); private String contextRoot;
private Router router;
private Map<Integer, WebExceptionHandler> exceptionHandlers = new HashMap<>();
private WebExceptionHandler defaultExceptionHandler = new DefaultExceptionHandler(); private WebTemplateEngine templateEngine = new WebTemplateEngine() {
}; static class DefaultExceptionHandler implements WebExceptionHandler { @Override
public void handle(ApplicationContext ctx, AbortException e) {
if (e.getStatus().code() == ) {
LOG.error("Internal Server Error", e);
}
ctx.error(e.getContent(), e.getStatus().code());
} } public RequestDispatcherImpl(Router router) {
this("/", router);
} public RequestDispatcherImpl(String contextRoot, Router router) {
this.contextRoot = CurrentUtil.normalize(contextRoot);
this.router = router;
} public RequestDispatcherImpl templateRoot(String templateRoot) {
this.templateEngine = new FreemarkerEngine(templateRoot);
return this;
} public String root() {
return contextRoot;
} public RequestDispatcherImpl exception(int code, WebExceptionHandler handler) {
this.exceptionHandlers.put(code, handler);
return this;
} public RequestDispatcherImpl exception(WebExceptionHandler handler) {
this.defaultExceptionHandler = handler;
return this;
}
@Override
public void dispatch(ChannelHandlerContext channelCtx, FullHttpRequest req) {
ApplicationContext ctx = new ApplicationContext(channelCtx, contextRoot, templateEngine);
try {
this.handleImpl(ctx, new Request(req));
} catch (AbortException e) {
this.handleException(ctx, e);
} catch (Exception e) {
this.handleException(ctx, new AbortException(HttpResponseStatus.INTERNAL_SERVER_ERROR, e));
} finally {
req.release();
}
} private void handleException(ApplicationContext ctx, AbortException e) {
WebExceptionHandler handler = this.exceptionHandlers.getOrDefault(e.getStatus().code(), defaultExceptionHandler);
try {
handler.handle(ctx, e);
} catch (Exception ex) {
this.defaultExceptionHandler.handle(ctx, new AbortException(HttpResponseStatus.INTERNAL_SERVER_ERROR, ex));
}
} private void handleImpl(ApplicationContext ctx, Request req) throws Exception {
if (req.decoderResult().isFailure()) {
ctx.abort(, "http protocol decode failed");
}
if (req.relativeUri().contains("./") || req.relativeUri().contains(".\\")) {
ctx.abort(, "unsecure url not allowed");
}
if (!req.relativeUri().startsWith(contextRoot)) {
throw new AbortException(HttpResponseStatus.NOT_FOUND);
}
req.popRootUri(contextRoot);
router.handle(ctx, req);
}
}

项目github位置

https://github.com/developerxiaofeng/WebFrameByNetty.git

实现基于netty的web框架,了解一下的更多相关文章

  1. 这样基于Netty重构RPC框架你不可能知道

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365天原创计划”第5天. 今天呢!灯塔君跟大家讲: 基于Netty重构RPC框架 一.CyclicBarrier方法说明 1. ...

  2. DIY一些基于netty的开源框架

    几款基于netty的开源框架,有益于对netty的理解和学习! 基于netty的http server框架 https://github.com/TogetherOS/cicada 基于netty的即 ...

  3. Netty高性能web框架

    框架背景: 前期为公司项目做全链路压测,发现公司跑到tomcat上的服务,即使是最简单的方法QPS也就到3000左右,后期查询发现可能和tomcat的业务逻辑有关. 因为以前在项目开发中用netty做 ...

  4. 基于 CSS 的 Web 框架 CJSS

    CJSS 是一个基于 CSS 的 Web 框架,所有效果都在 CSS 文件中生效,可以在 CSS 中使用它添加更多功能,或者构建一个完整的页面. 使用方法: HTML 想要使用某个组件,在 CSS 文 ...

  5. 基于Netty重构RPC框架

    下面的这张图,大概很多小伙伴都见到过,这是Dubbo 官网中的一张图描述了项目架构的演进过程.随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在 ...

  6. 《Java 编写基于 Netty 的 RPC 框架》

    一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞 ...

  7. java编写基于netty的RPC框架

    一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...

  8. 基于netty实现rpc框架-spring boot服务端

    demo地址 https://gitee.com/syher/grave-netty RPC介绍 首先了解一下RPC:远程过程调用.简单点说就是本地应用可以调用远程服务器的接口.那么通过什么方式调用远 ...

  9. Django视频教程 - 基于Python的Web框架(全13集)

    Django是由Python驱动的开源模型-视图-控制器(MVC)风格的Web应用程序框架,使用Django可以在即可分钟内快速开发一个高品质易维护数据库驱动的应用程序.下面是一大坨关于Django应 ...

随机推荐

  1. 基于Docker搭建大数据集群(六)Hive搭建

    基于Docker搭建大数据集群(六)Hive搭建 前言 之前搭建的都是1.x版本,这次搭建的是hive3.1.2版本的..还是有一点细节不一样的 Hive现在解析引擎可以选择spark,我是用spar ...

  2. APP自动化测试的环境配置

    什么是Appium? 第三方自动化框架(工具),扩充了selenium webdriver 协议,在原有的基础上添加了移动端测试API selenium webdriver 指定了客户端到服务端的协议 ...

  3. 用Python构造ARP请求、扫描、欺骗

    目录 0. ARP介绍 1. Scapy简述 2. Scapy简单演示 2.1 安装 2.2 构造包演示 2.2.1 进入kamene交互界面 2.2.2 查看以太网头部 2.2.3 查看 ICMP ...

  4. 【SQL server初级】数据库性能优化三:程序操作优化

    数据库优化包含以下三部分,数据库自身的优化,数据库表优化,程序操作优化.此文为第三部分 数据库性能优化三:程序操作优化 概述:程序访问优化也可以认为是访问SQL语句的优化,一个好的SQL语句是可以减少 ...

  5. JS/JQuery 禁用超链接a

    JS // 禁用超链接元素elem ; document.getElementById(elemID).setAttribute('disabled', 'disabled'); // 启用超链接元素 ...

  6. 12-z-index

    z-index 这个东西非常简单,它有四大特性,每个特性你记住了,页面布局就不会出现找不到盒子的情况. z-index 值表示谁压着谁,数值大的压盖住数值小的, 只有定位了的元素,才能有z-index ...

  7. Go语言入门教程(十一)

    原创: IT干货栈 Hello,各位小伙伴大家好,我是小栈君,昨天讲了关于go语言的函数的定义和自定函数的部分种类,我们今天接着上期所讲的内容继续进行分享. 一.函数之无参有返回值 有返回值的函数,必 ...

  8. 品Spring:关于@Scheduled定时任务的思考与探索,结果尴尬了

    非Spring风格的代码与Spring的结合 现在的开发都是基于Spring的,所有的依赖都有Spring管理,这没有问题. 但是要突然写一些非Spring风格的代码时,可能会很不习惯,如果还要和Sp ...

  9. linux shell 统计当前目录下的文件个数

    shell 统计当前目录下文件个数,使用管道组合命令: ls -1 | wc -l 解释: ls -1 表示一行一个列出文件名. wc -l 表示打印统计的行数. 两个命令通过管道连在一起表示打印列出 ...

  10. Android NDK(二) CMake构建工具进行NDK开发

    本文目录 一Androidstudio中需要的插件 二项目配置 ①build.gardle配置 ②CMakeLists.txt ③Android和Cpp的代码 ④so文件生成 ⑤so文件的位置 一.A ...