ByteBuf

ByteBuf

为了简化ByteBuffer的操作,Netty自定义了ByteBuf,相对于ByteBuffer的读写索引共用,Netty将其分开,定义了读索引和写索引,避免了每次读写都需要flip方法来转换。并且定义了扩容方法,解决了ByteBuffer容量无法修改的问题。

基本属性

  • readerIndex : 读索引
  • writerIndex : 写索引
  • markReaderIndex : 标记读索引位置
  • markWriterIndex : 标记写索引位置
  • readableBytes : 可读字节大小(writerIndex - readerIndex)
  • writableBytes : 可写字节大小(capacity - writerIndex)
  • resetReaderIndex : 还原到标记读索引位置
  • resetWriterIndex : 还原到标记写索引位置

池化:将用过的对象保存,重复使用。

堆外内存:使用JVM堆栈外的内存,该内存JVM无法干预,JVM的GC只能回收ByteBuf对象本身,而不能回收其指向的堆外内存,所以需要手动释放。

Netty的ByteBuf实现分为池化和非池化,其中又分为堆内存和堆外内存实现。由于堆外内存需要手动释放,所以需要自己维护引用数来标记是否释放。

Netty线程模型

服务端线程模型

Netty的服务端线程模型类似与Reactor多线程模型。采用的是Acceptor线程和IO线程分离的模式。将连接请求处理和读写处理分开,避免了因读写阻塞而导致无法处理客户端连接请求,导致客户端连接超时。
Netty服务端线程模型

  • 有一个专门的线程池(Acceptor)用来处理客户端的连接请求。
  • 有一个专门的线程池(IO)用来处理读写操作。
  • 一个线程处理N条连接,一个连接对应一个线程。

从Acceptor线程池中随机选择一个线程处理客户端连接请求,连接建立后将创建的SocketChannel注册到IO线程池中的某个线程上,由该线程负责SocketChannel的读写和编解码工作。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Acceptor线程池
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// IO线程池
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1000)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new EchoServerHandler());
}
});

// Start the server.
ChannelFuture f = b.bind(PORT).sync();

// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}

Netty服务端Bind-Accept流程

NIO深入

Buffer

NIO中的Buffer用于和NIOChannel进行交互,数据从Channel中读到Buffer中,数据从Buffer中写入到Channel中。Buffer本质是是一块可读写的内存区域,这块内存区域被包装成Buffer对象,并提供了一系列方法访问该内存。Buffer不是线程安全的,多线程时需要同步。

基础属性

  • 容量capacity : 缓冲区能够容纳的数据的最大数量,在创建的时候设定,不可再更改。
  • 上界limit : 缓冲区现存数据的计数,用来读写转换记录写的位置。
  • 位置position : 下一个要被读写的元素的索引。每次读写都会更新位置。
  • 标记mark : 备忘位置,用于重置位置。
    0 <= mark <= position <= limit <= capacity

重要方法

  • allocate : 创建一个Buffer对象,参数为capacity。
  • wrap :使用包装方式创建Buffer对象。
  • duplicate : 创建一个与原始Buffer相似的Buffer,两者共享数据,但是每个Buffer都有各自的position和limit。
  • slice : 创建一个切片Buffer,共享数据。
  • flip :转换读写状态,每次转换都将position的值赋给limit,将position设为0。从写转读,是从0开始读到limit(也就是已写数据)。从读转写,会将limit的值赋给position,也就是抵消了flip的操作,还原到上上次,从上次写的位置开始写。
  • remaining :limit-position,未读数据大小。
  • clear : 清空Buffer,position设为0,limit设为capacity的值。数据并未清除。可以重新开始写数据。
  • compact : 将未读的数据拷贝到Buffer头部,将position设为未读数据后面,这样未读数据会保留,重写数据从其后面开始写。
  • mark : 标记。
  • reset : 恢复到mark的position。
  • equals : 两个Buffer相等的条件,1.类型相同,2.Buffer中剩余的数据个数相同,3.Buffer中剩余的数据相同。

实现类型,Buffer的实现类型分为两种。

  • direct类型:采用直接内存实现,直接内存不受JVM控制,是由系统直接分配的,JVM无法GC回收,必须手动回收该部分内存。
  • heap类型:采用JVM堆栈实现。

大端、小端
每个基本数据类型都是以连续字节序列的形式存储在内存中。如果字节的最高字节在左边,位于低位地址中,则就是大端字节顺序。如果字节的最低字节在左边,位于低位地址中,则就是小端字节顺序。

TCP讲解

TCP是一个面向连接的、可靠的、流协议。在OSI模型中属于传输层协议。

序列号

序列号是按顺序给发送数据的每个字节都标上的编号。接收端查询接收数据TCP首部的序列号和数据的长度,计算出自己下一步应该接收的序列号,将其作为确认应答返回给发送端。

确认应答

当发送端的数据到达接收端主机时,接收端主机会返回一个已经收到消息的通知,这个消息叫做确认应答(ACK)。如果在一定时间内没有收到ACK,发送端认为可能丢包,则进行重发。接收端主机如果收到相同数据会放弃接收该数据,不会造成重复接收相同数据的情况。

超时重发

TCP每次发包都会计算往返时间及其偏差,并将这个时间作为重发超时的时间,如果超过该时间仍然没有收到接收端的确认应答,则认为发送超时,准备重发。如果重发一定次数仍然失败,则判断网络异常,强制关闭连接。
因为有重发机制,所以数据并不是一发送就从缓冲区中删除,而是等到接收端的确认应答返回,确认该数据已经被接收,才会从缓冲区删除。

连接管理

三次握手

TCP在数据通信之前,通过TCP首部发送一个SYN包作为建立连接的请求并等待确认应答,如果接收端发来确认应答,则认为可以进行数据通信,并向接收端返回确认应答。因此连接的建立需要来回发送三个包才能完成。

  1. 客户端发送SYN(向服务端请求建立连接)
  2. 服务端发送SYN/ACK(通知客户端服务端已经准备好了)
  3. 客户端回应ACK(通知服务端客户端已经准备好了)

三次握手后,双方都知道对方已经准备好了,连接建立。
TCP三次握手

四次挥手

在通信结束后也会进行断开连接的处理,发送FIN包给接收端。由于TCP是全双工模式,可以单端发送数据,所以需要4次挥手。
2、3步不能合并,是因为此时服务端可能还有数据没有发送,不能立刻就关闭,所以先发送ACK回应,等数据处理完再发送FIN请求客户端关闭。

  1. 客户端发送FIN/ACK(向服务端请求断开连接,标明客户端没有数据需要发送,可以关闭,但是还可以接收服务端数据)
  2. 服务端回应ACK(通知客户端可以关闭连接)
  3. 服务端发送FIN/ACK(向客户端请求断开连接,标明服务端没有数据需要发送,可以关闭)
  4. 客户端回应ACK(通知服务端可以关闭连接,服务端关闭连接,客户端等待一段时间后也关闭连接)

TCP四次挥手

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×