五种网络模型

https://cloud.tencent.com/developer/article/1586243
https://blog.csdn.net/woaixiaopangniu521/article/details/70279143
https://tech.meituan.com/2016/11/04/nio.html
https://www.jianshu.com/p/dde17c2d2e50

站在服务端的视角,对于一次 Socket 的数据读取操作流程,网络数据到达网卡,数据先被拷贝到内核缓冲区中,然后从内核缓冲区拷贝到进程用户空间。
站在服务端的视角,当一个读操作发生时,稍微再细化一下,其实会经历两个阶段。

第一阶段:等待数据准备。
例如:recv() 等待数据,需要等待网络上的数据分组到达,然后被复制到内核的缓冲区。

第二阶段:将数据从内核缓冲区拷贝到用户空间。
例如:recv() 接收连接发送的数据后,需要把数据复制到内核缓冲区,再从内核缓冲区复制到进程用户空间。

也正因为存在这两个阶段,Linux系统升级迭代中出现了五种网络 IO 模型。
①阻塞 IO 模型 - Blocking IO
当应用进程调用了 recv() 这个系统调用,内核就开始了 IO 的第一个阶段:准备数据。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞。
当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后内核返回结果,用户进程才解除阻塞的状态。

②非阻塞 I/O 模型 - Non-Blocking IO(并不是说的Java的NIO,Java的NIO不是利用这个IO模型实现的)
当用户进程发出recv()操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个错误码。
一旦内核中的数据准备好了,并且又再次收到了用户进程的系统调用,那么它马上就将数据拷贝到了用户内存,然后返回。

③I/O多路复用 - IO multiplexing(Linux 内核代码迭代过程中,多路复用的网络I/O模型依次支持了 SELECT(不停的轮询)、POLL(不停的轮询)、EPOLL(系统通知)三种。JavaNIO的实现是基于这个模型,也是常说的Reactor模式模式)
以 select 为例,当用户进程调用了 select,那么整个进程会被阻塞,而此时,内核会监视所有 select 负责的 socket,
当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 recv 操作,将数据从内核拷贝到用户进程。
对应JavaNIO的实现就是,当调用socketchannel.read的时候其实是阻塞的,

④信号驱动 I/O - Signal driven IO
应用进程使用 sigaction 系统调用,预先告知内核,向内核注册这样一个函数,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。
内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recv() 将数据从内核复制到应用进程中。

⑤异步 I/O 模型 - Asynchronous IO
应用进程执行 aio_read() 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
异步IO模型,要求等待数据和数据拷贝操作的两个处理阶段上都不能等待(blocking),内核自行去准备好数据并将数据从内核缓冲区中复制到应用进程的缓冲区,
再通知应用进程读操作完成了,然后应用进程再去处理。

遗憾的是,Linux 的网络 IO 模型中是不存在异步IO的,AIO的底层实现仍使用EPOLL。Linux的网络IO处理的第二阶段总是阻塞等待数据copy完成的。

下面详细说一下NIO,这里的NIO不是说的上面的非阻塞I/O模型,而是JavaNIO,基于I/O多路复用模型实现的.
NIO的主要事件有几个:读就绪、写就绪、有新连接到来。

我们首先需要注册当这几个事件到来的时候所对应的处理器。然后在合适的时机告诉事件选择器:我对这个事件感兴趣。对于写操作,就是写不出去的时候对写事件感兴趣;对于读操作,就是完成连接和系统没有办法承载新读入的数据的时;对于accept,一般是服务器刚启动的时候;而对于connect,一般是connect失败需要重连或者直接异步调用connect的时候。

其次,用一个死循环选择就绪的事件,会执行系统调用(Linux2.6之前是select、poll,2.6之后是epoll,Windows是IOCP),还会阻塞的等待新事件的到来。新事件到来的时候,会在selector上注册标记位,标示可读、可写或者有连接到来。

注意,select是阻塞的,无论是通过操作系统的通知(epoll)还是不停的轮询(select,poll),这个函数是阻塞的。所以你可以放心大胆地在一个while(true)里面调用这个函数而不用担心CPU空转。
所以我们的程序大概的模样是:

interface ChannelHandler{
void channelReadable(Channel channel);
void channelWritable(Channel channel);
}
class Channel{
Socket socket;
Event event;//读,写或者连接
}

//IO线程主循环:
class IoThread extends Thread{
public void run(){
Channel channel;
while(channel=Selector.select()){//选择就绪的事件和对应的连接
if(channel.eventaccept){
registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器
}
if(channel.event
write){
getChannelHandler(channel).channelWritable(channel);//如果可以写,则执行写事件
}
if(channel.event==read){
getChannelHandler(channel).channelReadable(channel);//如果可以读,则执行读事件
}
}
}
Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器
}

这个程序很简短,也是最简单的Reactor模式:注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。

协程的实现.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值