具体原理解析:
https://blog.csdn.net/xiangzhihong8/article/details/52029446/
0 netty与tomcat的异同
摘自https://www.oschina.net/question/1765708_158026
我的架构最开始是 nginx + [netty (tcp) ...] 用nginx做负载均衡。后来分布式接口变成了http restful -> nginx + [netty(http) ...]
于是参考了netty的官方http例子,开始移植代码,其实移植量不大,只需要把原来的tpc handler里面的逻辑放到http handler里面就可以了。本以为ok了,测试才发现了大量的问题,其实问题本身是由于对http基础掌握的还不够透彻,比如keep-alive属性,这个平时基本不用的东西,当你要自己开发http server时,变的很有用,你不能每次都close channel.你要化很多时间了解header里面每个字段的意思,然后去实现它。再比如一个post请求,你要自己写代码从body里面读数据,解码等。
最终我放弃了netty,回到了tomcat.应为tomcat把http的一切都帮你搞定了。
最终的架构是 nginx + [tomcat(http) ...]
tomcat就是针对http层的,所以我建议http还是选择tomcat(或者其他成熟的http-server),并不是说netty不好,而是你的选择问题,netty是一个网络组件,tcp,udp,http都可以弄,但是官方文档都是些hello wolrd级别的。如果你非常了解http结构,完全可以基于netty搞出一个比tomcat牛的http server.
如果做tcp开发,netty不二之选!
总结为:
Netty是基于Java NIO开发的,而Tomcat是Apache下的针对HTTP的服务器项目,前者更像一个中间件框架,后者更像一个工具
理解:netty自己内部用,tomcat对外。
1、request的流程处理
1.1 实现:
只需要在netty的pipeLine中配置HttpRequestDecoder和HttpObjectAggregator。
1.2 原理:
- 1:如果把解析这块理解是一个黑盒的话,则输入是ByteBuf,输出是FullHttpRequest。通过该对象便可获取到所有与http协议有关的信息。
- 2:HttpRequestDecoder先通过RequestLine和Header解析成HttpRequest对象,传入到HttpObjectAggregator。然后再通过body解析出httpContent对象,传入到HttpObjectAggregator。当HttpObjectAggregator发现是LastHttpContent,则代表http协议解析完成,封装FullHttpRequest。
- 3:对于body内容的读取涉及到Content-Length和trunked两种方式。两种方式只是在解析协议时处理的不一致,最终输出是一致的。
2 response的流程处理
2.1实现
只需要在netty的pipeLine中配置HttpResponseEncoder
2.2原理
- 1:输入是FullHttpResponse对象,输出是ByteBuf。socket再将ByteBuf数据发送到访问端。
- 2:对FullHttpResponse按照http协议进行序列化。判断header里面是ContentLength还是Trunked,然后body按照相应的协议进行序列化。
- 3:具体原理和request请求方式比较类似,这次不再详细描述。
3 netty http服务器代码展示
3.1 server
package com.suirui.http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by zongx on 2019/12/26.
*/
public class HttpServer {
// 工作线程池,http消息处理都由这个线程池进行
ExecutorService httpExecutor = Executors.newFixedThreadPool(8);
public static void main(String[] args) {
int port ;
if (args != null && args.length > 0) {
port = Integer.valueOf(args[0]);
} else {
port = 8090;
}
System.out.println(port);
new HttpServer().bind(port);
}
public void bind(int port) {
//配置服务端的NIO线程组
//两个线程组的原因是:
// 一个用于服务端接收客户端连接
//一个用于进行socketChannel网络读写
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
//ServerBootstrap 是netty的辅助启动类
ServerBootstrap b = new ServerBootstrap();
// .group传入两个线程组
// .channel设置创建的Channel类型为NioServerSocketChannel
// .option 配置NioServerSocketChannel的TCP参数
// .childHandler绑定IO事件的处理类 类似reactor模式中的handler:进行连接与读写socket。
// ChildChannelHandler会重写initChannel,保证当创建NioServerSocketChannel成功之后,再进行初始化。
/*************.handler 与 .childHandler的区别*******/
// handler()和childHandler()的主要区别是,handler()是发生在初始化的时候,childHandler()是发生在客户端连接之后。
//ChannelOption.SO_BACKLOG是输入连接指示(对连接的请求)的最大队列长度被设置为 backlog 参数。如果队列满时收到连接指示,则拒绝该连接。
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChildChannelHandler());
//绑定端口,等待成功
try {
//.sync()是同步阻塞接口,等待绑定操作完成
//ChannelFuture主要用于异步操作的通知回调
ChannelFuture f = b.bind(port).sync();
//等待服务端监听端口关闭
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
//注意此处不要使用ServerSocketChannel,否则客户端无法启动端口
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//继承了ChannelHandlerAppender,并且创建了一个HttpRequestDecode和一个HttpResponseEncoder
ch.pipeline().addLast(new HttpServerCodec());
// 目的是将多个消息转换为单一的request或者response对象,参数为聚合后http请求的大小线程
ch.pipeline().addLast(new HttpObjectAggregator(64 * 1024));
//目的是支持异步大文件传输()
ch.pipeline().addLast(new ChunkedWriteHandler());
//业务逻辑
ch.pipeline().addLast(new HttpServerRequestHandler(httpExecutor,"/http/"));
}
}
}
3.2 业务逻辑handler
package com.suirui.http;
import com.alibaba.fastjson.JSONObject;
import com.suirui.http.controller.HttpHandlerThread;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import org.apache.log4j.Logger;
import java.util.concurrent.ExecutorService;
/**
* Created by zongx on 2019/12/31.
*/
public class HttpServerRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static Logger logger = Logger.getLogger(HttpServerRequestHandler.class);
private final String httpPrefix;
private ExecutorService httpExecutors;
public HttpServerRequestHandler(ExecutorService httpExecutors, String httpPrefix) {
this.httpPrefix = httpPrefix;
this.httpExecutors = httpExecutors;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
String uri = msg.getUri();
//判断前端来的url路径是否正确
if (uri.startsWith(httpPrefix)) {
JSONObject json = new JSONObject();
json.put("code", 200);
json.put("message", "操作成功");
//获取要进行的操作与json参数
int index = uri.indexOf("?");
String op = uri.substring(httpPrefix.length(), index == -1 ? uri.length() : index);
String param = (index == -1) ? "" : uri.substring(index + 1);
String body = "";
//获取请求体
int len = msg.content().readableBytes();
if (len > 0) {
byte[] content = new byte[len];
msg.content().readBytes(content);
body = new String(content, "UTF-8");
}
//通过多线程线程池将处理结果返还
HttpHandlerThread t = new HttpHandlerThread(
msg.getProtocolVersion(),
HttpHeaders.isKeepAlive(msg),
ctx.channel(),
param,
body,
op);
httpExecutors.execute(t);
}
}
}