一、什么是netty
Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,
用于建立TCP等底层的连接,基于Netty可以建立高性能的Http服务器。支持HTTP、 WebSocket 、Protobuf、 Binary TCP 和UDP。
优点:并发高,传输快,封装好。
三大核心组件:
(1)channel通道
类似 IO 中的流,用于读取和写入。读操作的时候将 Channel 中的数据填充到 Buffer 中,而写操作时将 Buffer 中的数据写入到 Channel 中。
FileChannel:文件通道,用于文件的读和写。
DatagramChannel:用于UDP连接的接收和发送
SocketChannel:TCP客户端
ServerSocketChannel:TCP服务端,监听某个端口进来的请求。
(2)Selector选择器
NIO采用选择器(Selector)返回已经准备好的socket,并按顺序处理,基于通道(Channel)和缓冲区(Buffer)来进行数据的传输。
为了实现Selector管理多个SocketChannel,必须将具体的SocketChannel对象注册到Selector,并声明需要监听的事件(这样Selector才知道需要记录什么数据),一共有4种事件:
1、connect:客户端连接服务端事件,对应值为SelectionKey.OPCONNECT(8)
2、accept:服务端接收客户端连接事件,对应值为SelectionKey.OPACCEPT(16)
3、read:读事件,对应值为SelectionKey.OPREAD(1)
4、write:写事件,对应值为SelectionKey.OPWRITE(4)
这个很好理解,每次请求到达服务器,都是从connect开始,connect成功后,服务端开始准备accept,准备就绪,开始读数据,并处理,最后写回数据返回。
所以,当SocketChannel有对应的事件发生时,Selector都可以观察到,并进行相应的处理
(3)ByteBuf
以上就是一个 ByteBuf 的结构图,从上面这幅图可以看到
ByteBuf 是一个字节容器,容器里面的的数据分为三个部分,第一个部分是已经丢弃的字节,这部分数据是无效的;第二部分是可读字节,这部分数据是 ByteBuf 的主体数据, 从 ByteBuf 里面读取的数据都来自这一部分;最后一部分的数据是可写字节,所有写到 ByteBuf 的数据都会写到这一段。最后一部分虚线表示的是该 ByteBuf 最多还能扩容多少容量
以上三段内容是被两个指针给划分出来的,从左到右,依次是读指针(readerIndex)、写指针(writerIndex),然后还有一个变量 capacity,表示 ByteBuf 底层内存的总容量
从 ByteBuf 中每读取一个字节,readerIndex 自增1,ByteBuf 里面总共有 writerIndex-readerIndex 个字节可读, 由此可以推论出当 readerIndex 与 writerIndex 相等的时候,ByteBuf 不可读
写数据是从 writerIndex 指向的部分开始写,每写一个字节,writerIndex 自增1,直到增到 capacity,这个时候,表示 ByteBuf 已经不可写了
ByteBuf 里面其实还有一个参数 maxCapacity,当向 ByteBuf 写数据的时候,如果容量不足,那么这个时候可以进行扩容,直到 capacity 扩容到 maxCapacity,超过 maxCapacity 就会报错
Netty 使用 ByteBuf 这个数据结构可以有效地区分可读数据和可写数据,读写之间相互没有冲突,当然,ByteBuf 只是对二进制数据的抽象,具体底层的实现我们在下面的小节会讲到,在这一小节,我们 只需要知道 Netty 关于数据读写只认 ByteBuf
二、Netty为什么并发高?
1、阻塞IO(BIO)的通信方式
在BIO中,等待客户端发数据这个过程是阻塞的,这样就造成了一个线程只能处理一个请求的情况,而机器能支持的最大线程数是有限的,这就是为什么BIO不能支持高并发的原因。
BIO会有两次阻塞,
第一次:serverSocket等待客户端的连接,每当产生一个连接,就会分配一个socket进行处理。serverSocket.accept();
第二次:获取了socket链接后,从socket那里把inputStream管道拿出来,然后从inputStream管道那里等待用户数据时产生了阻塞。in.read(buffer);
代码如下:
public class BioServerSingle { //blocking
public static void main(String[] args) {
// 服务端开启一个端口进行监听
int port = 8080;
ServerSocket serverSocket = null; //服务端
Socket socket; //客户端
InputStream in = null;
OutputStream out = null;
try {
serverSocket = new ServerSocket(port);
//通过构造函数创建ServerSocket,指定监听端口,
//如果端口合法且空闲,服务器就会监听成功
// 通过无限循环监听客户端连接,如果没有客户端接入,
//则会阻塞在accept操作
while (true) {
System.out.println("Waiting for a new Socket" +
" ," + new Date().toString());
socket = serverSocket.accept();//阻塞 三次握手
in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer)) > 0) {//阻塞
System.out.println("input is:" +
new String(buffer, 0, length) +
" ," + new Date().toString());
out = socket.getOutputStream();
out.write("success".getBytes());
System.out.println("Server end" +
" ," + new Date().toString());
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 必要的清理活动
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
BIO处理流程
2、非阻塞式IO(NIO)通信方式
NIO的单线程能处理连接的数量比BIO要高出很多,而为什么单线程能处理更多的连接呢?
当一个Socket建立好之后,Thread并不会阻塞去接受这个Socket,而是将这个请求交给Selector,Selector会不断的去遍历所有的Socket,一旦有一个Socket建立完成,他会通知Thread,然后Thread处理完数据再返回给客户端这个过程是不阻塞的,这样就能让一个Thread处理更多的请求了。
简单的netty代码
package com.kkagr.netty.firstExample;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class TestService {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workGroup).
channel(NioServerSocketChannel.class)
.childHandler(new TestServerinitializer());
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
package com.kkagr.netty.firstExample;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject msg) throws Exception {
if(msg instanceof HttpRequest){
HttpRequest httpRequest = (HttpRequest)msg;
System.out.println("请求方法名"+httpRequest.method().name());
ByteBuf content = Unpooled.copiedBuffer("hello word", CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH,
content.readableBytes());
channelHandlerContext.writeAndFlush(response);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
super.channelActive(ctx);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
super.channelRegistered(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelUnregistered");
super.channelUnregistered(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
super.channelInactive(ctx);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
super.channelReadComplete(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
System.out.println("userEventTriggered");
super.userEventTriggered(ctx, evt);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelWritabilityChanged");
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exceptionCaught");
super.exceptionCaught(ctx, cause);
}
}
package com.kkagr.netty.firstExample;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
public class TestServerinitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline =socketChannel.pipeline();
//编码解码合二为一
pipeline.addLast("httpServerCodec",new HttpServerCodec());
pipeline.addLast("testHttpServerHandler",new TestHttpServerHandler());
}
}
启动main方法,在浏览器输入http://localhost:8899/
NIO的处理流程
三、Netty为什么传输快?
Netty的传输快其实也是依赖了NIO的一个特性——零拷贝。
Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用内存空间最大的一块,也是Java对象存放的地方,一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点,如果数据量大,就会造成不必要的资源浪费。
使用了NIO,当他需要接收数据的时候,他会在堆内存之外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面通过ByteBuf可以直接对这些数据进行直接操作,从而加快了传输速度。
四、Netty为什么封装好?
Channel
数据传输流,与channel相关的概念有以下四个,上一张图让你了解netty里面的Channel。
ByteBuf
ByteBuf是一个存储字节的容器,最大特点就是使用方便,它既有自己的读索引和写索引,方便你对整段字节缓存进行读写,也支持get/set,方便你对其中每一个字节进行读写,他的数据结构如下图所示:
他有三种使用模式:
Heap Buffer 堆缓冲区
堆缓冲区是ByteBuf最常用的模式,他将数据存储在堆空间。
Direct Buffer 直接缓冲区
直接缓冲区是ByteBuf的另外一种常用模式,他的内存分配都不发生在堆,jdk1.4引入的nio的ByteBuffer类允许jvm通过本地方法调用分配内存,这样做有两个好处
通过免去中间交换的内存拷贝, 提升IO处理速度; 直接缓冲区的内容可以驻留在垃圾回收扫描的堆区以外。
DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的内存, GC对此”无能为力”,也就意味着规避了在高负载下频繁的GC过程对应用线程的中断影响.
Composite Buffer 复合缓冲区
复合缓冲区相当于多个不同ByteBuf的视图,这是netty提供的,jdk不提供这样的功能。
除此之外,他还提供一大堆api方便你使用,在这里我就不一一列出了,具体参见ByteBuf字节缓存。
Codec
Netty中的编码/解码器,通过他你能完成字节与pojo、pojo与pojo的相互转换,从而达到自定义协议的目的。
在Netty里面最有名的就是HttpRequestDecoder和HttpResponseEncoder了。
注:侵权告知,立马删除
三大组件内容来源:
作者:猎户星座。
链接:https://blog.csdn.net/qq_24313635/article/details/103340028
来源:CSDN
概念内容来源:
作者:追那个小女孩
链接:https://www.jianshu.com/p/b9f3f6a16911
来源:简书
NIO代码例子来源:
作者:kkagr
原文链接:https://blog.csdn.net/kkagr/article/details/94293500
来源:CSDN