上一篇写了,基于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. Mysql学习笔记整理之选用B+tree结构

    为什么mysql不使用平衡二叉树? 数据处的深度决定着他的IO操作次数,IO操作耗时大 每一个磁盘块保存的数据量太小 B+Tree和B-Tree的区别? B+树几点关键字搜索采用闭合区间 B+树非叶节 ...

  2. thinkphp将上传的临时文件移动到指定目录

    thinkphp将上传的临时文件移动到指定目录 新建common.php文件 <?phpuse think\facade\Env; /** 移动上传的临时文件 * * @img_dir stri ...

  3. Cocos Creator实现左右跳游戏

    ​1. 玩法说明 游戏开始后,点击屏幕左右两侧,机器人朝左上方或右上方跳一步,如果下一步有石块,成功得1分,否则游戏结束. 2. 模块介绍 游戏场景分为2个:主页场景(home).游戏场景(game) ...

  4. Spring框架学习笔记(3)——SpringMVC框架

    SpringMVC框架是基于Spring框架,可以让我们更为方便的进行Web的开发,实现前后端分离 思路和原理 我们之前仿照SpringMVC定义了一个自定义MVC框架,两者的思路其实都是一样的. 建 ...

  5. cp -rf 操作时依然会提示覆盖

    在linux上经常会使用cp -rf命令,该命令就是强制覆盖指定目录下的文件,但有时使用该命令依然会提示是否覆盖,cp命令的参数有如下一些: 参数说明: -a:此选项通常在复制目录时使用,它保留链接. ...

  6. .net core 3.0 Signalr - 01 基础篇

    因为将signalr作为单独的站点,此处需要建立两个项目,一个专门用于signalr作为推送项目,一个客户端(实际的业务项目) ## 基础知识速览 ### Clients对象属性 | 属性 | 描述 ...

  7. YiShaAdmin,基于.NET Core Web开源的后台快速开发框架

    YiShaAdmin YiShaAdmin 基于.NET Core Web开发,借鉴了很多开源项目的优点,让你开发Web管理系统和移动端Api更简单,所以我也把她开源了. 她可以用于所有的Web应用程 ...

  8. 更该clover软件图标(任务栏显示)

    1.首先介绍一个Clover软件,Clover 的功能就是给资源管理器加上 Chrome 一样的标签页,像下面这样,你会爱上它的效率,和浏览器一样的操作方式. 2.它自带的系统图标比较丑(虽然作者说挺 ...

  9. Spring Boot (十二): Spring Boot 邮件服务

    最早我们发邮件的时候是使用 JavaMail 来发送邮件,而在 Spring Boot 中, Spring Boot 帮我们将 JavaMail 封装好了,是可以直接拿来使用的. 1. 依赖文件 po ...

  10. Formform

    知识预览 一 Django的form组件 二 Django的model form组件 三 Django的缓存机制 四 Django的信号 五 Django的序列化 回到顶部 一 Django的form ...