网络IO系统

本文深入探讨了网络传输的优化技术,包括零拷贝的三种实现:传统的文件传输方式、mmap()以及sendfile()。mmap()减少了CPU拷贝,而sendfile()进一步降低到一次系统调用。此外,介绍了IO多路复用的select、poll和epoll机制,以及它们如何提高服务器处理大量并发请求的能力。最后,概述了reactor模式在简化多路复用编程中的应用。
摘要由CSDN通过智能技术生成

目录

1 零拷贝技术

1.1 传统的文件传输方式

1.2 mmap()

1.3 sendfile()

2 IO多路复用(select/poll/epoll)

2.1 传统的基于阻塞IO的多进程/所线程模型

2.2 IO多路复用

3 reactor模式


1 零拷贝技术

1.1 传统的文件传输方式

传统的网络文件传输流程如下:

  • 将磁盘中的数据拷贝到内核缓冲区中(DMA拷贝)

  • 将内核缓冲区中的数据拷贝到用户空间中(CPU拷贝)

  • 将数据从用户空间再拷贝到socket缓冲区中(CPU拷贝)

  • 将socket中的数据拷贝到网卡中(DMA拷贝)

前面两次过程涉及到一次read()系统调用,后面两次过程涉及到一次write()系统调用,整个过程共有两次系统调用,两次CPU拷贝和两次DMA拷贝,可见性能是非常差的,所以需要对传统的文件传输方式进行优化

1.2 mmap()

所谓的mmap技术就是将read()系统调用换成了mmap()系统调用,所谓的mmap()函数就是将用户空间与内核空间的缓冲区做了一个直接映射,这样我们的数据就可以省去一次CPU拷贝(read系统调用中的内核缓冲区->用户空间数据)这种网络传输模式还是涉及到两次系统调用,分别是mmap()和write()。具体流程如下:

  • 将磁盘中的数据拷贝到内核缓冲区中(DMA拷贝)

  • 将内核缓冲区中的数据拷贝到socket缓冲区(CPU拷贝)

  • 将socket缓冲区的数据拷贝到网卡中(DMA拷贝)

可见性能相对于传统的网络IO有了减少一次CPU拷贝的提升

1.3 sendfile()

即使使用了mmap()技术我们还是涉及到两次系统调用,所以则是就有了sendfile()函数,调用一次该函数就能够完成一次网络传输过程,具体流程如下:

  • 将磁盘中的数据拷贝到内核缓冲区中(DMA拷贝)

  • 将内核缓冲区中的数据拷贝到socket缓冲区(CPU拷贝)

  • 将socket缓冲区的数据拷贝到网卡中(DMA拷贝)

数据的拷贝过程和mmap()+write()是一致的,但是只有一次系统调用了,相应的,在这个基础上,支持SG-DMA技术的网卡能够再进一步提升IO性能,过程如下:这也是真正的零拷贝技术

  • 将磁盘中的数据拷贝到内核缓冲区中(DMA拷贝)

  • 将socket缓冲区的数据拷贝到网卡中(SG-DMA拷贝)

2 IO多路复用(select/poll/epoll)

2.1 传统的基于阻塞IO的多进程/所线程模型

传统的socket编程我们的服务器每接受到一个请求都会建立socket连接然后将其交给一个线程或者进程来处理,传统的socket编程就是基于阻塞IO实现的,阻塞IO的实现过程如下:

  • 用户线程发起read系统调用,用户线程此时阻塞

  • 内核准备数据

  • 内核将数据拷贝到用户进程

  • 拷贝完毕,read系统调用返回

整个过程我们的用户线程都是阻塞的,阻塞IO网络socket编程的实现过程如下:

  • 服务端绑定端口监听

  • 客户端发起连接请求

  • 服务端将与客户端建立好的socket放入到一个队列中

  • 当服务端调用accept函数的时候就会创建一个线程成接受这个socket并处理客户端的读写(read和write)请求

存在的问题:

  • 当请求量非常多时需要创建很多的线程,这样线程上下文切换的开销非常大

  • 通过线程池创建线程会导致无法同时处理大量的请求

2.2 IO多路复用

由于传统的socket编程我们每要处理一个socket就得创建一个线程与之对应,所以人们想到能否让一个线程监听多个socket,一个线程处理多个socket的请求,这就是我们的IO多路复用,这里说明一下,多路是说多个TCP连接,或者说多个socket,复用指的是多个socket被一个或者一个线程组来处理。IO多路复用涉及到三个函数,分别是select,poll和epoll,介绍如下:

select/poll

select IO多路复用的步骤如下:

  • 将已连接的socket放到一个文件描述符集合中,然后将其拷贝到内核中,由内核来帮我们监听这些socket

  • 内核监听socket的方式非常简单,直接遍历真个文件描述符集合,将发生网络事件的socket进行读写标记

  • 内核将标记后的socket文件描述符集合拷贝到用户空间

  • 用户程序对socket文件描述符集合进行再次遍历,处理有读写标记的socket

select和poll的区别:

二者的主要区别就是对于socket文件描述符集合的存储方式上,select是将文件描述符存储在一个固定大小的BitMap中,所以文件描述符集合的大小有限制;poll将文件描述符存储在一个动态数组中,底层是一个链表,也就解决了select函数socket文件描述符集合有大小限制的问题;

epoll

epoll(epoll对象通过epoll_create()函数创建)不再采用线性结构来组织socket文件描述符集合,而是将socket文件描述符通过红黑树组织起来(epoll_ctl()函数),当某个socket上有事件发生时,会将该socket取出后放到一个链表中,然后用户再通过epoll_wait()系统调用就可以得到这个就绪事件链表然后处理事件即可,这样就避免了和select/poll一样拷贝全部socket文件描述符集合并进行遍历,大大提高了事件处理效率。

事件触发机制的不同

select/poll只支持水平触发,epoll还支持边缘触发

水平触发的意思是只要满足事件的条件,比如内核中有数据需要读,就一直不断地把这个事件传递给用户;而边缘触发的意思是只有第一次满足条件的时候才触发,之后就不会再传递同样的事件了,使用边缘出触发时我们必须保证一次读取数据时就全部读取完毕。

3 reactor模式

传统的IO多路复用接口编程都是面向过程的方式,为了简化编程,所以基于面向对象的思想创建了reactor模式,这个模式有三个主要类型:

  • 单reactor单进程/线程 redis

    1. reactor通过selectIO多路复用监听事件

    2. 监听到事件后将事件进行dispatch分发

    3. 连接请求的时间分配给acceptor处理并创建一个handler处理后序事件;其他请求则直接分配给handler

    4. handler通过read -> 业务处理 -> send的方式来完成业务流程

  • 单reactor多线程

    1. reactor通过select监听事件

    2. 发生事件后进行dispatch分发

    3. 将连接请求交给acceptor来处理;其他请求直接交给我们的handler处理

    4. handler会read -> 创建一个processor线程处理业务逻辑 -> processor处理完后handler进行send

  • 多reactor多线程 netty

    1. mainReactor通过select监听事件

    2. 监听到事件后将请求dispatch

    3. 连接请求交由acceptor,并将建立好的连接分配给subReactor

    4. subReactor会对这些连接进行监听,有事件发生则dispatch给handler进行处理

    5. handler通过read -> 业务处理 -> send的方式处理业务逻辑

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值