Netty(二)

1.零拷贝

什么是零拷贝?

为了更好地理解问题的解决方案,我们首先需要理解问题本身。让我们来看看什么是参与网络服务器的简单过程dæmon服务数据存储在一个文件通过网络客户端。下面是一些示例代码:

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

看起来两行代码很简单不会带来很大开销。但实际上在两个调用后,数据至少复制了四次,并且执行了4次用户/内核上下文切换。如图
在这里插入图片描述

  1. 当read系统调用时,导致上下文从用户模式切换到内核模式。由DMA引擎执行,它从磁盘读取文件内容并将其存储到内核地址空间缓冲区中。
  2. 将数据从内核缓冲区复制到用户缓冲区,read系统调用返回。调用的返回导致上下文从内核切换回用户模式
  3. write系统调用导致上下文从用户模式切换到内核模式,执行第三次复制,将数据复制到socket buffer
  4. write系统调用返回,创建我们的第四个上下文切换。当DMA引擎将数据从内核缓冲区传递到协议引擎时,会独立地、异步地进行第四次复制

可以看出上述过程有很多没有必要的复制,所以为了消除开销,我们可以从消除内核和用户缓冲区之间的一些复制开始。

2.mmap

消除副本的一种方法是跳过调用read,而是调用mmap

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

在这里插入图片描述

  1. mmap系统调用导致DMA引擎将文件内容复制到内核缓冲区。然后与用户进程共享缓冲区,而不需要在内核和用户内存空间之间执行任何复制
  2. write系统调用导致内核将原始内核缓冲区中的数据复制到与套接字相关的内核缓冲区中
  3. 当DMA引擎将数据从内核套接字缓冲区传递到协议引擎时,发生第三次复制。

3.Sendfile

在内核版本2.4中,修改了套接字缓冲区描述符以适应那些需求——在Linux下称为零拷贝。这种方法不仅减少了多个上下文切换,还消除了处理器造成的数据重复。对于用户级应用程序,一切都没有改变,所以代码仍然是这样的

sendfile(socket, file, len);

在这里插入图片描述

  1. sendfile系统调用导致DMA引擎将文件内容复制到内核缓冲区。
  2. 没有数据被复制到套接字缓冲区。相反,只有包含关于数据位置和长度信息的描述符才会被附加到套接字缓冲区中。DMA引擎直接将数据从内核缓冲区传递到协议引擎,从而消除了剩余的最终副本

因为数据实际上仍然是从磁盘复制到内存,从内存复制到连接,所以有些人可能会认为这不是真正的零拷贝。但是,从操作系统的角度来看,这是零拷贝,因为数据不是在内核缓冲区之间复制的。

2. IO模型

因为学习Netty必不可少的要了解IO多路复用模型,接下来介绍常见IO模型

1. 名词概念

  • 阻塞:指向调用方,在调用结果返回之前,调用方线程会挂起,直到结果返回。
  • 非阻塞:指向调用方,在调用结果返回之前,调用方线程会处理其他事情,不会阻塞。
  • 同步:指向被调用方,被调用方得到结果后再返回给调用方。
  • 异步:指向被调用方,被调用方先应答调用方,然后计算结果,最终通知并返回给调用方。
  • recvfrom函数:系统调用,经socket接收数据。

2. 模型

1.阻塞式IO模型

在这里插入图片描述
进程调用recvfrom函数,在数据没有返回之前,进程阻塞,直到数据返回后,才会处理数据。

2.非阻塞式IO模型

在这里插入图片描述
进程调用recvfrom函数,如果数据没有准备好就返回错误提示,之后进程循环调用recvfrom函数,直到有数据返回。

3.IO/复用模型

在这里插入图片描述
进程调用select,如果没有套接字变为可读,则阻塞,直到有可读套接字之后,调用recvfrom函数,返回结果。

4.信号驱动式IO模型

在这里插入图片描述
进程先注册信号驱动,之后进程不阻塞,当数据准备好后,会给进程返回信号提示,这时进程调用ecvfrom函数,返回数据。

5.异步IO模型

在这里插入图片描述
由POSIX规范定义,应用程序告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到应用程序的缓冲区)完成后通知应用程序。这种模型与信号驱动模型的主要区别在于:信号驱动I/O是由内核通知应用程序何时启动一个I/O操作,而异步I/O模型是由内核通知应用程序I/O操作何时完成。

IO模型对比

在这里插入图片描述

3.线程模型

1.传统阻塞IO服务模型

在这里插入图片描述

  • 采用阻塞IO模型获取输入的数据。 每个连接需要独立的完成数据的输入,业务的处理,数据返回。
  • 当并发数大的时候,会创建大量的线程,占用系统资源,如果连接创建后,当前线程没有数据可读,会阻塞,造成线程资源浪费。

2.Reactor模型

这里重点介绍主从Reactor多线程模型
在这里插入图片描述

  • Reactor主线程的MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件。
  • 当Acceptor处理完连接事件之后,MainReactor将连接分配给SubReactor。
  • SubReactor将连接加入到连接队列进行监听,并创建handler进行事件处理。
  • 当有新的事件发生时,SubReactor就会调用对应的handler处理。
  • handler通过read读取数据,交由Worker线程池处理业务。
  • Worker线程池分配线程处理完数据后,将结果返回给handler。
  • handler收到返回的数据后,通过send将结果返回给客户端。
  • MainReactor可以对应多个SubReactor。

特别说明的是:虽然 Netty 的线程模型基于主从 Reactor 多线程,借用了 MainReactor 和 SubReactor 的结构。但是实际实现上 SubReactor 和 Worker 线程在同一个线程池中:

  • bossGroup 线程池则只是在 Bind 某个端口后,获得其中一个线程作为
    MainReactor,专门处理端口的 Accept 事件,每个端口对应一个 Boss 线程。
  • workerGroup 线程池会被各个 SubReactor 和 Worker 线程充分利用。

4.异步处理

  • Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。
  • 调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
  • 当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作

常见有如下操作

  • 通过 isDone 方法来判断当前操作是否完成。
  • 通过 isSuccess 方法来判断已完成的当前操作是否成功。
  • 通过 getCause 方法来获取已完成的当前操作失败的原因。
  • 通过 isCancelled 方法来判断已完成的当前操作是否被取消。
  • 通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则理解通知指定的监听器。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值