有了Netty,你能够实现本身的HTTP服务器,FTP服务器,UDP服务器,RPC服务器,WebSocket服务器,Redis的Proxy服务器,MySQL的Proxy服务器等等。 java
咱们回顾一下传统的HTTP服务器的原理 api
一、建立一个ServerSocket,监听并绑定一个端口数组
二、一系列客户端来请求这个端口缓存
三、服务器使用Accept,得到一个来自客户端的Socket链接对象 安全
四、启动一个新线程处理链接 bash
4.一、读Socket,获得字节流服务器
4.二、解码协议,获得Http请求对象 网络
4.三、处理Http请求,获得一个结果,封装成一个HttpResponse对象 多线程
4.四、编码协议,将结果序列化字节流 写Socket,将字节流发给客户端 并发
五、继续循环步骤3
HTTP服务器之因此称为HTTP服务器,是由于编码解码协议是HTTP协议,若是协议是Redis协议,那它就成了Redis服务器,若是协议是WebSocket,那它就成了WebSocket服务器,等等。 使用Netty你就能够定制编解码协议,实现本身的特定协议的服务器。
上面是一个传统处理http的服务器,可是在高并发的环境下,线程数量会比较多,System load也会比较高,因而就有了NIO。
说NIO以前先说一下BIO(Blocking IO),如何理解这个Blocking呢?
传统的BIO模式下,从头至尾的全部线程都是阻塞的,这些线程就干等着,占用系统的资源,什么事也不干。
那么NIO是怎么作到非阻塞的呢。它用的是事件机制。它能够用一个线程把Accept,读写操做,请求处理的逻辑全干了。若是什么事都没得作,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。用伪代码表示:while true {
events = takeEvents(fds) // 获取事件,若是没有事件,线程就休眠
for event in events {
if event.isAcceptable {
doAccept() // 新连接来了
} elif event.isReadable {
request = doRead() // 读消息
if request.isComplete() {
doProcess()
}
} elif event.isWriteable {
doWrite() // 写消息
}
}
}复制代码
Reactor单线程模型
一个NIO线程+一个accept线程:
Reactor多线程模型
Reactor主从模型
Netty能够基于如上三种模型进行灵活的配置。
Netty是创建在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象。
在Netty里面,Accept链接可使用单独的线程池去处理,读写操做又是另外的线程池来处理。
Accept链接和读写操做也可使用同一个线程池来进行处理。而请求处理逻辑既可使用单独的线程池进行处理,也能够跟放在读写线程一块处理。线程池中的每个线程都是NIO线程。用户能够根据实际状况进行组装,构造出知足系统需求的高性能并发模型。
若是不用netty,使用原生JDK的话,有以下问题:
而Netty来讲,他的api简单、性能高并且社区活跃(dubbo、rocketmq等都使用了它)
先看以下代码,这个代码是使用netty在client端重复写100次数据给server端,ByteBuf是netty的一个字节容器,里面存放是的须要发送的数据
public class FirstClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 1000; i++) {
ByteBuf buffer = getByteBuf(ctx);
ctx.channel().writeAndFlush(buffer);
}
}
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
byte[] bytes = "你好,个人名字是1234567!".getBytes(Charset.forName("utf-8"));
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(bytes);
return buffer;
}
}复制代码
从client端读取到的数据为:
从服务端的控制台输出能够看出,存在三种类型的输出
应用层面使用了Netty,可是对于操做系统来讲,只认TCP协议,尽管咱们的应用层是按照 ByteBuf 为 单位来发送数据,server按照Bytebuf读取,可是到了底层操做系统仍然是按照字节流发送数据,所以,数据到了服务端,也是按照字节流的方式读入,而后到了 Netty 应用层面,从新拼装成 ByteBuf,而这里的 ByteBuf 与客户端按顺序发送的 ByteBuf 多是不对等的。所以,咱们须要在客户端根据自定义协议来组装咱们应用层的数据包,而后在服务端根据咱们的应用层的协议来组装数据包,这个过程一般在服务端称为拆包,而在客户端称为粘包。
在没有 Netty 的状况下,用户若是本身须要拆包,基本原理就是不断从 TCP 缓冲区中读取数据,每次读取完都须要判断是不是一个完整的数据包 若是当前读取的数据不足以拼接成一个完整的业务数据包,那就保留该数据,继续从 TCP 缓冲区中读取,直到获得一个完整的数据包。 若是当前读到的数据加上已经读取的数据足够拼接成一个数据包,那就将已经读取的数据拼接上本次读取的数据,构成一个完整的业务数据包传递到业务逻辑,多余的数据仍然保留,以便和下次读到的数据尝试拼接。
而在Netty中,已经造好了许多类型的拆包器,咱们直接用就好:
选好拆包器后,在代码中client段和server端将拆包器加入到chanelPipeline之中就行了:
如上实例中:
客户端:
ch.pipeline().addLast(new FixedLengthFrameDecoder(31));复制代码
服务端:
ch.pipeline().addLast(new FixedLengthFrameDecoder(31));复制代码
是在发送数据的时候,传统的实现方式是:
1. `File.read(bytes)`
2. `Socket.send(bytes)`
这种方式须要四次数据拷贝和四次上下文切换:
1. 数据从磁盘读取到内核的read buffer
2. 数据从内核缓冲区拷贝到用户缓冲区明显上面的第二步和第三步是没有必要的,经过java的FileChannel.transferTo方法,能够避免上面两次多余的拷贝(固然这须要底层操做系统支持)
Netty发送和接收消息主要使用bytebuffer,bytebuffer使用对外内存(DirectMemory)直接进行Socket读写。
以上就是我对Netty相关知识整理,若是有不一样的看法,欢迎讨论!