网络IO与IO多路复用

news2025/1/18 9:48:07

一、网络IO基础

  • 系统对象
    • 网络IO涉及用户空间调用IO的进程或线程以及内核空间的内核系统。
    • 例如,当进行read操作时,会经历两个阶段:
      1. 等待数据准备就绪。
      2. 将数据从内核拷贝到进程或线程中。
  • 多种网络IO模型的出现原因:由于上述两个阶段的不同情况,出现了多种网络IO模型。

二、阻塞IO(blocking IO)

  • 特点
    • 在Linux中,默认所有socket都是阻塞的。
    • 以读操作为例,当用户进程调用read系统调用,kernel开始IO的第一阶段(准备数据),对于网络IO,数据可能未到,kernel要等待,用户进程会被阻塞。
    • 直到kernel等到数据准备好,将数据从kernel拷贝到用户内存并返回结果,用户进程才解除阻塞状态。
    • 即阻塞IO在等待数据和拷贝数据两个阶段都阻塞进程。
    • 例如,使用listen()send()recv()等接口构建服务器/客户机模型,这些接口大多是阻塞型的。
    • 代码示例:
// 创建服务器端的socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
// 绑定地址
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = INADDR_ANY;
bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
// 监听连接
listen(serverSocket, 5);
// 接受连接
int clientSocket = accept(serverSocket, NULL, NULL);
// 接收数据,在此处进程会阻塞,直到接收到数据或出错
char buffer[1024];
recv(clientSocket, buffer, sizeof(buffer), 0);
// 发送数据,在此处进程也可能阻塞
send(clientSocket, buffer, strlen(buffer), 0);
- 解释:上述代码首先创建了一个服务器端的socket,然后绑定地址和端口,接着监听连接。当调用`accept`时,如果没有连接请求,会阻塞等待。一旦有连接,调用`recv`接收数据时,会阻塞直到有数据到达。发送数据时也可能阻塞,例如网络拥堵或接收方接收缓冲区满。
  • 多线程/多进程改进方案
    • 为解决单个连接阻塞影响其他连接的问题,可在服务器端使用多线程或多进程。
    • 多线程可使用pthread_create()创建,多进程使用fork()创建。
    • 多线程开销相对小,适合为较多客户机服务;进程更安全,适合单个服务执行体需要大量CPU资源的情况。
    • 例如,服务器端为多个客户机提供服务时,可在主线程等待连接请求,有连接时创建新线程或新进程提供服务。
    • 但当需要同时响应大量(成百上千)连接请求时,多线程或多进程会严重占用系统资源,导致系统效率下降和假死。

三、非阻塞IO(non-blocking IO)

  • 特点
    • 通过设置socket为非阻塞,如使用fcntl( fd, F_SETFL, O_NONBLOCK );
    • 当用户进程发出read操作,若kernel数据未准备好,不会阻塞用户进程,而是立即返回error
    • 用户进程可根据返回结果判断数据是否准备好,未准备好可再次发送read操作。
    • 当数据准备好,kernel会将数据拷贝到用户内存并返回。
    • 例如,recv()接口在非阻塞状态下调用后立即返回,返回值有不同含义:
      • recv()返回值大于 0,表示接受数据完毕,返回值是接收到的字节数。
      • recv()返回 0,表示连接正常断开。
      • recv()返回 -1,且errno等于EAGAIN,表示recv操作未完成。
      • recv()返回 - 1,且errno不等于EAGAIN,表示recv操作遇到系统错误errno
    • 代码示例:
// 创建服务器端的socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
// 设置为非阻塞
fcntl(serverSocket, F_SETFL, O_NONBLOCK);
// 绑定地址
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = INADDR_ANY;
bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
// 监听连接
listen(serverSocket, 5);
// 接受连接
int clientSocket = accept(serverSocket, NULL, NULL);
// 接收数据,此处不会阻塞,会根据返回值判断状态
char buffer[1024];
int recvResult;
while ((recvResult = recv(clientSocket, buffer, sizeof(buffer), 0)) == -1 && errno == EAGAIN) {
    // 数据未准备好,可进行其他操作或再次尝试接收
}
if (recvResult > 0) {
    // 处理接收到的数据
} else if (recvResult == 0) {
    // 连接断开
} else {
    // 其他错误
}
- 解释:在这个示例中,将服务器端socket设置为非阻塞后,调用`recv`时不会阻塞进程。如果`recv`返回-1且`errno`为`EAGAIN`,说明数据还未准备好,程序可继续执行其他操作或过段时间再尝试接收,而不是像阻塞IO那样一直等待。

四、多路复用IO(IO multiplexing)

  • 特点
    • 又称事件驱动IO(event driven IO),如select/epoll
    • 单个进程可同时处理多个网络连接的IO,基本原理是select/epoll函数不断轮询所负责的所有socket,当某个socket有数据到达,通知用户进程。
    • 流程:用户进程调用select会被阻塞,kernel监视select负责的socket,当有socket数据准备好,select返回,用户进程再调用read操作将数据从kernel拷贝到用户进程。
    • 虽然使用select需要两个系统调用(selectread),但优势是可在一个线程内处理多个socket的IO请求。
    • 代码示例:
#include <stdio.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(serverSocket, 5);
    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(serverSocket, &readfds);
    int maxFd = serverSocket;
    while (1) {
        fd_set tempFds = readfds;
        int activity = select(maxFd + 1, &tempFds, NULL, NULL, NULL);
        if (FD_ISSET(serverSocket, &tempFds)) {
            int clientSocket = accept(serverSocket, NULL, NULL);
            FD_SET(clientSocket, &readfds);
            if (clientSocket > maxFd) {
                maxFd = clientSocket;
            }
        }
        for (int i = 0; i <= maxFd; i++) {
            if (FD_ISSET(i, &tempFds) && i!= serverSocket) {
                char buffer[1024];
                int valread = recv(i, buffer, 1024, 0);
                if (valread == 0) {
                    close(i);
                    FD_CLR(i, &readfds);
                } else {
                    // 处理接收到的数据
                }
            }
        }
    }
    return 0;
}
- 解释:首先创建服务器socket,绑定并监听。`FD_ZERO`和`FD_SET`用于初始化和设置文件描述符集合。`select`函数会阻塞,等待集合中文件描述符的可读事件。当`serverSocket`有新连接时,添加新连接的socket到集合,并更新最大文件描述符。当其他socket可读时,接收数据并处理。
  • 接口原型
    • FD_ZERO(int fd, fd_set* fds):初始化fd_set
    • FD_SET(int fd, fd_set* fds):将句柄添加到fd_set
    • FD_ISSET(int fd, fd_set* fds):检查句柄是否在fd_set中。
    • FD_CLR(int fd, fd_set* fds):从fd_set中移除句柄。
    • int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout):用于探测多个文件句柄的状态变化,readfdswritefdsexceptfds作为输入和输出参数,可设置超时时间。
  • 问题
    • 当需要探测的句柄值较大时,select()接口本身需要大量时间轮询,很多操作系统提供更高效接口如epoll(Linux)、kqueue(BSD)、/dev/poll(Solaris)等。
    • 该模型将事件探测和事件响应夹杂,若事件响应执行体庞大,会降低事件探测的及时性。
    • 可使用事件驱动库如libeventlibev库解决上述问题。

五、异步IO(Asynchronous I/O)

  • 特点
    • Linux下的异步IO主要用于磁盘IO读写操作,从内核2.6版本开始引入。
    • 用户进程发起read操作后可做其他事,kernel收到asynchronous read后立刻返回,不会阻塞用户进程。
    • kernel等待数据准备完成,将数据拷贝到用户内存,完成后给用户进程发送信号通知。
    • 代码示例(使用aio_read):
#include <aio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

void aio_completion_handler(union sigval sigval) {
    struct aiocb *req = (struct aiocb *)sigval.sival_ptr;
    if (aio_error(req) == 0) {
        char *buffer = (char *)malloc(aio_return(req));
        // 处理读取到的数据
        free(buffer);
    }
    aio_destroy(req);
}

int main() {
    int fd = open("test.txt", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    struct aiocb my_aiocb;
    memset(&my_aiocb, 0, sizeof(struct aiocb));
    my_aiocb.aio_fildes = fd;
    my_aiocb.aio_buf = malloc(1024);
    my_aiocb.aio_nbytes = 1024;
    my_aiocb.aio_offset = 0;
    my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
    my_aiocb.aaiocb.sigev_notify_function = aio_completion_handler;
    my_aiocb.aio_sigevent.sigev_notify_attributes = NULL;
    my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;
    if (aio_read(&my_aiocb) < 0) {
        perror("aio_read");
        close(fd);
        return 1;
    }
    // 进程可做其他事情
    sleep(1);
    return 0;
}
- 解释:上述代码使用`aio_read`进行异步读操作。首先打开文件,设置`aiocb`结构(包含文件描述符、缓冲区、字节数等),并设置信号通知方式和处理函数。调用`aio_read`后,进程可以继续做其他事情,当读操作完成,会调用`aio_completion_handler`处理结果。
  • 重要性:异步IO是真正非阻塞的,对高并发网络服务器实现至关重要。

六、信号驱动IO(signal driven I/O, SIGIO)

  • 特点
    • 允许套接口进行信号驱动I/O并安装信号处理函数,进程继续运行不阻塞。
    • 当数据准备好,进程收到SIGIO信号,可在信号处理函数中调用I/O操作函数处理数据。
    • 优势在于等待数据报到达期间,进程可继续执行,避免了select的阻塞与轮询。

七、服务器模型Reactor与Proactor

  • Reactor模型
    • 是一种事件驱动机制,用于同步I/O。
    • 应用程序将处理I/O事件的接口注册到Reactor上,若相应事件发生,Reactor调用注册的接口(回调函数)。
    • 三个重要组件:
      1. 多路复用器:如selectpollepoll等系统调用。
      2. 事件分发器:将多路复用器返回的就绪事件分到对应的处理函数。
      3. 事件处理器:负责处理特定事件的处理函数。
    • 具体流程:
      1. 注册读就绪事件和相应事件处理器。
      2. 事件分离器等待事件。
      3. 事件到来,激活分离器,分离器调用事件对应的处理器。
      4. 事件处理器完成实际的读操作,处理数据,注册新事件,返还控制权。
    • 优点:响应快,编程相对简单,可扩展性和可复用性好。
    • 缺点:当程序需要使用多核资源时会有局限,因为通常是单线程的。
    • 代码示例:
#include <iostream>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

class EventHandler {
public:
    virtual void handleEvent(int fd) = 0;
};

class Reactor {
private:
    int epollFd;
    std::vector<EventHandler*> handlers;
public:
    Reactor() {
        epollFd = epoll_create1(0);
    }
    ~Reactor() {
        close(epollFd);
    }
    void registerHandler(int fd, EventHandler* handler) {
        struct epoll_event event;
        event.data.fd = fd;
        event.events = EPOLLIN;
        epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &event);
        handlers.push_back(handler);
    }
    void handleEvents() {
        struct epoll_event events[10];
        int numEvents = epoll_wait(epollFd, events, 10, -1);
        for (int i = 0; i < numEvents; i++) {
            int fd = events[i].data.fd;
            for (EventHandler* handler : handlers) {
                handler->handleEvent(fd);
            }
        }
    }
};

class EchoHandler : public EventHandler {
public:
    void handleEvent(int fd) override {
        char buffer[1024];
        int bytesRead = recv(fd, buffer, sizeof(buffer), 0);
        if (bytesRead > 0) {
            send(fd, buffer, bytesRead, 0);
        }
    }
};

int main() {
    Reactor reactor;
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(serverSocket, 5);
    EchoHandler handler;
    reactor.registerHandler(serverSocket, &handler);
    while (1) {
        reactor.handleEvents();
    }
    return 0;
}
- 解释:上述C++代码中,`Reactor`类负责创建`epoll`,注册和处理事件。`EventHandler`是抽象基类,`EchoHandler`是具体处理接收和发送的派生类。`main`函数创建服务器socket,注册`EchoHandler`到`Reactor`,并不断调用`handleEvents`处理事件。
  • Proactor模型
    • 最大特点是使用异步I/O,所有I/O操作都交由系统的异步I/O接口执行,工作线程只负责业务逻辑。
    • 具体流程:
      1. 处理器发起异步操作并关注I/O完成事件。
      2. 事件分离器等待操作完成事件。
      3. 分离器等待时,内核并行执行实际I/O操作并将结果存入用户缓冲区,通知分离器读操作完成。
      4. I/O完成后,通过事件分离器呼唤处理器。
      5. 事件处理器处理用户缓冲区中的数据。
    • 增加了编程复杂度,但给工作线程带来更高效率,可利用系统态的读写优化。
    • 在Windows上常用IOCP支持高并发,Linux上因aio性能不佳,主要以Reactor模型为主。
    • 也可使用Reactor模拟Proactor,但在读写并行能力上会有区别。

八、同步I/O和异步I/O的区别总结

  • 阻塞与非阻塞IO的区别
    • 调用阻塞IO会阻塞进程直到操作完成,非阻塞IO在kernel准备数据时会立刻返回。
  • 同步与异步IO的区别
    • 同步IO在做“IO operation”(如read系统调用)时会阻塞进程。阻塞IO、非阻塞IO、多路复用IO都属于同步IO。
    • 非阻塞IO在数据准备好时的拷贝数据阶段会阻塞进程;而异步IO在整个过程中,进程不会被阻塞,进程发起IO操作后直接做其他事,直到kernel发送信号通知完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2278412.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型高并发部署方案探究

版本 内容 姓名 时间 V1.0 新建 xx 2025-01-16 声明&#xff1a;只是进行探究&#xff0c;后续真正实践后&#xff0c;会更新新的内容 前置条件&#xff1a;70B的模型&#xff0c;并发要求200 性能测试参考链接 Benchmarking LLM Inference Backends :表明一台A100(8…

Java并发06 - ThreadLocal详解

ThreadLocal详解 文章目录 ThreadLocal详解一&#xff1a;认识 ThreadLocal 线程局部存储1&#xff1a;ThreadLocal特点2&#xff1a;如何实现线程隔离3&#xff1a;继承父线程的局部存储4&#xff1a;自动清理与内存泄漏问题5&#xff1a;ThreadLocal使用场景6&#xff1a;阿里…

【设计模式】 单例模式(单例模式哪几种实现,如何保证线程安全,反射破坏单例模式)

单例模式 作用&#xff1a;单例模式的核心是保证一个类只有一个实例&#xff0c;并且提供一个访问实例的全局访问点。 实现方式优缺点饿汉式线程安全&#xff0c;调用效率高 &#xff0c;但是不能延迟加载懒汉式线程安全&#xff0c;调用效率不高&#xff0c;能延迟加载双重检…

【漏洞预警】FortiOS 和 FortiProxy 身份认证绕过漏洞(CVE-2024-55591)

文章目录 一、产品简介二、漏洞描述三、影响版本四、漏洞检测方法五、解决方案 一、产品简介 FortiOS是Fortinet公司核心的网络安全操作系统&#xff0c;广泛应用于FortiGate下一代防火墙&#xff0c;为用户提供防火墙、VPN、入侵防御、应用控制等多种安全功能。 FortiProxy则…

记录一次 centos 启动失败

文章目录 现场1分析1现场2分析2搜索实际解决过程 现场1 一次断电,导致 之前能正常启动的centos 7.7 起不来了有部分log , 关键信息如下 [1.332724] XFS(sda3): Internal error xfs ... at line xxx of fs/xfs/xfs_trans.c [1.332724] XFS(sda3): Corruption of in-memory data…

关于vite+vue3+ts项目中env.d.ts 文件详解

env.d.ts 文件是 Vite 项目中用于定义全局类型声明的 TypeScript 文件。它帮助开发者向 TypeScript提供全局的类型提示&#xff0c;特别是在使用一些特定于 Vite 的功能时&#xff08;如 import.meta.env&#xff09;。以下是详细讲解及代码示例 文章目录 **1. env.d.ts 文件的…

虚拟专用网VPN的概念及实现VPN的关键技术

虚拟专用网VPN通过建立在公共网络上的重要通道(1分),实现远程用户、分支机构、业务伙伴等与机构总部网络的安全连接&#xff0c;从而构建针对特定组织机构的专用网络&#xff0c;实现与专用网络类似的功能&#xff0c;可以达到PN安全性的目的&#xff0c;同时成本相对要低很多(…

将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch(1)

问题 项目里使用了 AzureBlob 存储了用户上传的各种资源文件&#xff0c;近期 AzureBlob 的流量费用增长很快&#xff0c;想通过分析Blob的日志&#xff0c;获取一些可用的信息&#xff0c;所以有了这个需求&#xff1a;将存储账户的日志&#xff08;读写&#xff0c;审计&…

X-12-ARIMA:季节性调整(Seasonal Adjustment)的强大工具,介绍数学原理

X-12-ARIMA&#xff1a;季节性调整的强大工具 在经济学、金融学以及各类统计数据分析中&#xff0c;季节性调整&#xff08;Seasonal Adjustment&#xff09;是一个至关重要的步骤。许多经济指标&#xff0c;如GDP、失业率和零售销售数据等&#xff0c;往往会受到季节性因素的…

.netframwork模拟启动webapi服务并编写对应api接口

在.NET Framework环境中模拟启动Web服务&#xff0c;可以使用几种不同的方法。一个常见的选择是利用HttpListener类来创建一个简单的HTTP服务器&#xff0c;或者使用Owin/Katana库来自托管ASP.NET Web API或MVC应用。下面简要介绍Owin/Katana示例代码。这种方法更加灵活&#x…

网络安全构成要素

一、防火墙 组织机构内部的网络与互联网相连时&#xff0c;为了避免域内受到非法访问的威胁&#xff0c;往往会设置防火墙。 使用NAT&#xff08;NAPT&#xff09;的情况下&#xff0c;由于限定了可以从外部访问的地址&#xff0c;因此也能起到防火墙的作用。 二、IDS入侵检…

免费送源码:Java+ssm+MySQL 基于PHP在线考试系统的设计与实现 计算机毕业设计原创定制

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对在线考试等问题&#xff0c;对如何通过计算…

html中鼠标位置信息

pageX&#xff1a;鼠标距离页面的最左边的距离&#xff0c;包括滚动条的长度。clientX&#xff1a;鼠标距离浏览器视口的左距离&#xff0c;不包括滚动条。offsetX&#xff1a;鼠标到事件源左边的距离。movementX&#xff1a;鼠标这次触发的事件的位置相对于上一次触发事件的位…

光谱相机的光谱分辨率可以达到多少?

多光谱相机 多光谱相机的光谱分辨率相对较低&#xff0c;波段数一般在 10 到 20 个左右&#xff0c;光谱分辨率通常在几十纳米到几百纳米之间&#xff0c;如常见的多光谱相机光谱分辨率为 100nm 左右。 高光谱相机 一般的高光谱相机光谱分辨率可达 2.5nm 到 10nm 左右&#x…

RAG 切块Chunk技术总结与自定义分块实现思路

TrustRAG项目地址&#x1f31f;&#xff1a;https://github.com/gomate-community/TrustRAG 可配置的模块化RAG框架 切块简介 在RAG&#xff08;Retrieval-Augmented Generation&#xff09;任务中&#xff0c;Chunk切分是一个关键步骤&#xff0c;尤其是在处理结构复杂的PDF文…

Java基础——概念和常识(语言特点、JVM、JDK、JRE、AOT/JIT等介绍)

我是一个计算机专业研0的学生卡蒙Camel&#x1f42b;&#x1f42b;&#x1f42b;&#xff08;刚保研&#xff09; 记录每天学习过程&#xff08;主要学习Java、python、人工智能&#xff09;&#xff0c;总结知识点&#xff08;内容来自&#xff1a;自我总结网上借鉴&#xff0…

Low-Level 大一统:如何使用Diffusion Models完成视频超分、去雨、去雾、降噪等所有Low-Level 任务?

Diffusion Models专栏文章汇总&#xff1a;入门与实战 前言&#xff1a;视频在传输过程中常常因为各种因素&#xff08;如恶劣天气、噪声、压缩和传感器分辨率限制&#xff09;而出现质量下降&#xff0c;这会严重影响计算机视觉任务&#xff08;如目标检测和视频监控&#xff…

矩阵碰一碰发视频源码技术开发全解析,支持OEM

在当今数字化内容传播迅速发展的时代&#xff0c;矩阵碰一碰发视频功能以其便捷、高效的特点&#xff0c;为用户分享视频提供了全新的体验。本文将深入探讨矩阵碰一碰发视频源码的技术开发过程&#xff0c;从原理到实现&#xff0c;为开发者提供全面的技术指引。 一、技术原理 …

测试工程师的linux 命令学习(持续更新中)

1.ls """1.ls""" ls -l 除文件名称外&#xff0c;亦将文件型态、权限、拥有者、文件大小等资讯详细列出 ls -l等同于 ll第一列共10位&#xff0c;第1位表示文档类型&#xff0c;d表示目录&#xff0c;-表示普通文件&#xff0c;l表示链接文件。…

HJ34 图片整理(Java版)

一、试题地址 图片整理_牛客题霸_牛客网 二、试题描述 描述 对于给定的由大小写字母和数字组成的字符串&#xff0c;请按照 ASCIIASCII 码值将其从小到大排序。 如果您需要了解更多关于 ASCIIASCII 码的知识&#xff0c;请参考下表。 输入描述&#xff1a; 在一行上输入一…