当前位置: 首页 > news >正文

TCP并发服务器多线程和多进程方式以及几种IO模型

1. 阻塞 I/O(Blocking I/O)

在阻塞 I/O 模型中,当应用程序发起 I/O 操作时,整个进程会被阻塞,直到操作完成。在这个过程中,应用程序无法执行其他任务,必须等待 I/O 操作的完成。

特点

  • 简单性:编程简单,逻辑清晰,容易理解和实现。
  • 低效性:在高并发场景下,由于每个 I/O 操作都会阻塞整个进程,资源利用率较低。

2. 非阻塞 I/O(Non-blocking I/O)

非阻塞 I/O 模型允许应用程序在发起 I/O 操作时立即返回,即使数据尚未准备好。应用程序可以在等待 I/O 完成的同时执行其他任务,需通过轮询(多次尝试)来检查 I/O 是否完成。

特点

  • 并发性:在等待 I/O 完成时,应用程序可以继续处理其他任务。
  • 轮询开销:需要频繁检查 I/O 状态,增加了 CPU 的负担。

3. I/O 多路复用(I/O Multiplexing)

I/O 多路复用(如 selectpollepoll)允许应用程序同时监听多个 I/O 事件,并在任何一个 I/O 操作准备好时被通知。应用程序可以集中处理多个 I/O 操作,从而避免轮询带来的开销。

特点

  • 高效性:适用于需要同时处理多个 I/O 连接的场景,尤其是高并发服务器。
  • 复杂性:编程复杂度高,需要仔细管理多个 I/O 描述符和事件。

4. 信号驱动 I/O(Signal-driven I/O)

信号驱动 I/O 模型中,应用程序发起 I/O 操作并继续执行其他任务,当数据准备好时,内核会通过信号通知应用程序。这种方式允许应用程序避免轮询和阻塞,且能够异步处理 I/O 事件。

特点

  • 异步性:内核通过信号通知应用程序,无需轮询。
  • 复杂性:信号处理逻辑复杂,容易出错。

5. 异步 I/O(Asynchronous I/O)

在异步 I/O 模型中,应用程序发起 I/O 操作并立即返回,I/O 操作由内核完成,操作完成后内核通过回调机制通知应用程序。应用程序无需等待 I/O 完成,也无需轮询或处理信号。

特点

  • 最高效:真正的异步模型,应用程序可以充分利用 CPU 时间。
  • 复杂性:编程难度较大,需要处理异步回调和并发问题。

1. 多线程并发服务器

在多线程模型中,服务器为每个客户端连接创建一个独立的线程。每个线程处理客户端的请求,并将处理结果返回给客户端。由于线程是在同一进程内执行的,因此它们共享内存空间和其他资源。

工作流程:
  1. 主线程监听:服务器在指定端口上监听客户端连接请求。
  2. 接受连接:当有新的客户端连接时,服务器接受该连接,并为其创建一个新的线程。
  3. 线程处理:新线程负责处理该客户端的所有请求,直到客户端断开连接。线程可以读取客户端发送的数据、进行处理,并将结果发送回客户端。
  4. 线程终止:在处理完毕后,线程可以选择继续等待新的请求(长连接)或终止(短连接)。
优点:
  • 资源共享:线程间共享同一进程的资源(如内存、文件描述符),使得在不同线程之间共享数据变得容易。
  • 响应速度快:创建线程的开销相对较低,线程切换也比进程切换快,适合需要快速响应的场景。
缺点:
  • 同步问题:由于线程共享同一地址空间,因此在访问共享资源时,容易出现数据竞争问题,需要使用同步机制(如互斥锁)来避免竞争条件,这会增加代码复杂度。
  • 稳定性:一个线程崩溃可能会影响整个进程,因为所有线程共享同一进程空间。
使用场景:
  • 高并发应用:适合需要处理大量并发连接的场景,如聊天室、实时通信系统。
  • 轻量级任务:当每个请求的处理时间较短时,多线程模型能够有效提高处理效率。

2. 多进程并发服务器

在多进程模型中,服务器为每个客户端连接创建一个独立的进程。每个进程在自己的内存空间中运行,处理来自客户端的请求并返回结果。由于进程是独立的,数据不会在进程之间共享。

工作流程:
  1. 主进程监听:服务器在指定端口上监听客户端连接请求。
  2. 接受连接:当有新的客户端连接时,服务器接受该连接,并为其派生一个新的子进程。
  3. 进程处理:子进程独立运行,处理客户端的请求,直到客户端断开连接。子进程在处理过程中可以读取数据、进行处理,并返回结果。
  4. 进程终止:子进程处理完毕后终止,释放相关资源。
优点:
  • 独立性强:每个进程都有独立的内存空间和资源,因此一个进程崩溃不会影响其他进程的运行,服务器整体的稳定性较高。
  • 安全性高:由于进程间的数据不共享,因此天然避免了线程间的竞争条件和同步问题。
缺点:
  • 资源开销大:创建和销毁进程的开销比线程大得多,尤其是在高并发场景下,进程的频繁创建和销毁可能会耗尽系统资源。
  • 进程通信复杂:如果进程之间需要通信,必须使用 IPC 机制(如管道、消息队列、共享内存等),这增加了开发的复杂度。
使用场景:
  • 高安全性应用:适合对安全性要求较高的场景,如需要严格隔离不同客户端的应用。
  • 长时间任务:适合处理时间较长、复杂度较高的任务,因为进程之间相互独立,不会因为某个进程的长时间运行影响到其他任务的处理。

  • 多线程并发服务器:适合轻量级、高并发的应用场景,能够快速响应请求,但需要注意线程同步问题和稳定性。
  • 多进程并发服务器:适合高安全性、高稳定性的场景,尤其是需要隔离不同任务的应用,但进程开销较大,进程间通信较为复杂。

 fcntl()

  • 原型int fcntl(int fd, int cmd, ... /* arg */ );

  • 用法

    1. 首先,使用 fcntl() 函数获取文件描述符的当前标志。
      • 可以通过传递 F_GETFL 作为 cmd 参数来实现。
    2. 然后,将非阻塞标志 O_NONBLOCK 添加到当前标志中。
    3. 最后,再次使用 fcntl() 函数将新的标志设置回文件描述符。
      • 可以通过传递 F_SETFL 作为 cmd 参数来实现

实现信号驱动 I/O 主要依赖以下函数:

1. 设置文件描述符为信号驱动模式

要将文件描述符设置为信号驱动模式,可以使用 fcntl() 函数。

  • fcntl()
    • 原型int fcntl(int fd, int cmd, ... /* arg */ );

    • 用法

      1. 首先,使用 fcntl() 函数获取文件描述符的当前标志。
        • 通过传递 F_GETFL 作为 cmd 参数来获取当前标志。
      2. 然后,将 O_ASYNC 标志添加到文件描述符的当前标志中。
        • 通过传递 F_SETFL 作为 cmd 参数,并将 O_ASYNC 与现有标志结合来实现。
      3. 最后,使用 fcntl() 设置信号接收进程或进程组:
        • 通过传递 F_SETOWN 作为 cmd 参数,并传递要接收信号的进程 ID 或进程组 ID。

      这样,当文件描述符有 I/O 事件发生时,系统将向指定进程发送 SIGIO 信号。

2. 处理信号

在信号驱动 I/O 中,当文件描述符准备好时,会触发 SIGIO 信号。应用程序需要设置信号处理程序来处理该信号。

  • sigaction()
    • 原型int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

    • 用法

      • 使用 sigaction() 函数为 SIGIO 信号设置一个处理函数。
      • 在信号处理函数中,应用程序可以执行相应的 I/O 操作(如读取或写入数据)。

      通过 sigaction() 配置 SIGIO 信号的处理程序后,应用程序在文件描述符准备好进行 I/O 操作时会自动收到通知并调用处理程序。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Python】copy()浅拷贝与深拷贝
  • 【牛客_c++_string】HJ1字符串最后一个单词的长度
  • Spring Boot使用拦截器(Interceptor)
  • mysql中group by语句使用
  • 结果一。6.will,begoingto,betodo,beabouttodo结构的区别
  • 在CentOS 7上安装MongoDB的方法
  • ROS imu传感器节点
  • 书生大模型实战营-进阶关卡-6-MindSearch 快速部署
  • 力扣8.27
  • 阿里云对象存储服务(Aliyun OSS):企业级云存储解决方案
  • Spring Boot 集成 JdbcTemplate(盘它!)
  • 敏捷架构开发方法和实践:迎接数字化时代的挑战
  • 东芝玉兔2.0明日震撼开售,洗衣机界的全新革命
  • 本地化云桌面系统环境VMware horizon搭建
  • Golang反射:运行时类型检查与操作
  • [译]Python中的类属性与实例属性的区别
  • 【Leetcode】104. 二叉树的最大深度
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • canvas绘制圆角头像
  • CSS实用技巧干货
  • log4j2输出到kafka
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • node入门
  • socket.io+express实现聊天室的思考(三)
  • vue:响应原理
  • 关于springcloud Gateway中的限流
  • 计算机常识 - 收藏集 - 掘金
  • 两列自适应布局方案整理
  • 每天10道Java面试题,跟我走,offer有!
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 微信小程序实战练习(仿五洲到家微信版)
  • 一个JAVA程序员成长之路分享
  • 译有关态射的一切
  • C# - 为值类型重定义相等性
  • Mac 上flink的安装与启动
  • 阿里云重庆大学大数据训练营落地分享
  • # 达梦数据库知识点
  • (02)Unity使用在线AI大模型(调用Python)
  • (1)Android开发优化---------UI优化
  • (1)虚拟机的安装与使用,linux系统安装
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (7) cmake 编译C++程序(二)
  • (C#)一个最简单的链表类
  • (day6) 319. 灯泡开关
  • (el-Date-Picker)操作(不使用 ts):Element-plus 中 DatePicker 组件的使用及输出想要日期格式需求的解决过程
  • (function(){})()的分步解析
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (独孤九剑)--文件系统
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (二刷)代码随想录第16天|104.二叉树的最大深度 559.n叉树的最大深度● 111.二叉树的最小深度● 222.完全二叉树的节点个数
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (四)Controller接口控制器详解(三)
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • .net core + vue 搭建前后端分离的框架