爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      Linux高级IO

      首页 知识中心 其他 文章详情页

      Linux高级IO

      2023-07-18 09:58:02 阅读次数:423

      centos,linux,网络

       

      IO的基本概念

      什么是IO?

      I/O(input/output)也就是输入和输出,在著名的冯诺依曼体系结构当中,将数据从输入设备拷贝到内存就叫做输入,将数据从内存拷贝到输出设备就叫做输出。
      Linux高级IO

      • 对文件进行的读写操作本质就是一种IO,文件IO对应的外设就是磁盘。
      • 对网络进行的读写操作本质也是一种IO,网络IO对应的外设就是网卡。

      OS如何得知外设当中有数据可读取?

      输入就是操作系统将数据从外设拷贝到内存的过程,操作系统一定要通过某种方法得知特定外设上是否有数据就绪。

      • 并不是操作系统想要从外设读取数据时外设上就一定有数据。比如用户正在访问某台服务器,当用户的请求报文发出后就需要等待从网卡当中读取服务器发来的响应数据,但此时对方服务器可能还没有收到我们发出的请求报文,或是正在对我们的请求报文进行数据分析,也有可能服务器发来的响应数据还在网络中路由。
      • 但操作系统不会主动去检测外设上是否有数据就绪,这种做法一定会降低操作系统的工作效率,因为大部分情况下外设当中都是没有数据的,因此操作系统所做的大部分检测工作其实都是徒劳的。
      • 操作系统实际采用的是中断的方式来得知外设上是否有数据就绪的,当某个外设上面有数据就绪时,该外设就会向CPU当中的中断控制器发送中断信号,中断控制器再根据产生的中断信号的优先级按顺序发送给CPU。
      • 每一个中断信号都有一个对应的中断处理程序,存储中断信号和中断处理程序映射关系的表叫做中断向量表,当CPU收到某个中断信号时就会自动停止正在运行的程序,然后根据该中断向量表执行该中断信号对应的中断处理程序,处理完毕后再返回原被暂停的程序继续运行。

      需要注意的是,CPU不直接和外设打交道指的是在数据层面上,而外设其实是可以直接将某些控制信号发送给CPU当中的某些控制器的。

      OS如何处理从网卡中读取到的数据包?

      操作系统任何时刻都可能会收到大量的数据包,因此操作系统必须将这些数据包管理起来。所谓的管理就是“先描述,再组织”,在内核当中有一个结构叫做sk_buff,该结构就是用来管理和控制接收或发送数据包的信息的。

      为了说明sk_buff的作用,下面给出一个简化版的sk_buff结构:
      Linux高级IO
      当操作系统从网卡当中读取到一个数据包后,会将该数据依次交给链路层、网络层、传输层、应用层进行解包和分用,最终将数据包中的数据交给了上层用户,那对应到这个sk_buff结构来说具体是如何进行数据包的解包和分用的呢?

      • 当操作系统从网卡中读取到一个数据包后,就会定义出一个sk_buff结构,然后用sk_buff结构当中的data指针指向这个读取到的数据包,并将定义出来的这个sk_buff结构与其他sk_buff结构以双链表的形式组织起来,此时操作系统对各个数据包的管理就变成了对双链表的增删查改等操作。
      • 接下来我们需要将读取上来的数据包交给最底层的链路层处理,进行链路层的解包和分用,此时就是让sk_buff结构当中的mac_header指针指向最初的数据包,然后向后读取链路层的报头,剩下的就是需要交给网络层处理的有效载荷了,此时便完成了链路层的解包,。
      • 这时链路层就需要将有效载荷向上交付给网络层进行解包和分用了,这里所说的向上交付只是形象的说法,实际向上交付并不是要将数据从链路层的缓冲区拷贝到网络层的缓冲区,我们只需要让sk_buff结构当中的network_header指针,指向数据包中链路层报头之后的数据即可,然后继续向后读取网络层的报头,便完成了网络层的解包。
      • 紧接着就是传输层对数据进行处理了,同样的道理,让sk_buff结构当中的transport_header指针,指向数据包中网络层报头之后的数据,然后继续向后读取传输层的报头,便完成了传输层的解包。
      • 传输层解包后就可以根据具体使用的传输层协议,对应将剩下的数据拷贝到TCP或UDP的接收缓冲区供用户读取即可

      Linux高级IO
      发送数据时对数据进行封装也是同样的道理,就是依次在数据前面拷贝上对应的报头,最后再将数据发送出去(UDP)或拷贝到发送缓冲区(TCP)即可。也就是说,数据包在进行封装和解包的过程中,本质数据的存储位置是没有发生变化的,我们实际只是在用不同的指针对数据进行操作而已。

      但内核中的sk_buff并不像上面那样简单:

      • 一方面,为了保证高效的网络报文处理效率,这就要求sk_buff的结构也必须是高效的。
      • 另一方面,sk_buff结构需要被内核协议中的各个协议共同使用,因此sk_buff必须能够兼容所有的网络协议。

      因此sk_buff结构实际是非常复杂的,在我的云服务器中sk_buff结构的定义如下:

      struct sk_buff {
      #ifdef __GENKSYMS__
      	/* These two members must be first. */
      	struct sk_buff          *next;
      	struct sk_buff          *prev;
      	ktime_t         tstamp;
      #else
      	union {
      		struct {
      			/* These two members must be first. */
      			struct sk_buff          *next;
      			struct sk_buff          *prev;
      
      			union {
      				ktime_t         tstamp;
      				struct skb_mstamp skb_mstamp;
      				__RH_KABI_CHECK_SIZE_ALIGN(ktime_t a,
      				struct skb_mstamp b);
      			};
      		};
      		struct rb_node  rbnode; /* used in netem, ip4 defrag, and tcp stack */
      	};
      #endif
      	struct sock             *sk;
      	struct net_device       *dev;
      
      	/*
      	* This is the control buffer. It is free to use for every
      	* layer. Please put your private variables there. If you
      	* want to keep them across layers you have to do a skb_clone()
      	* first. This is owned by whoever has the skb queued ATM.
      	*/
      	char                    cb[48] __aligned(8);
      
      	unsigned long           _skb_refdst;
      #ifdef CONFIG_XFRM
      	struct  sec_path        *sp;
      #endif
      	unsigned int            len,
      		data_len;
      	__u16                   mac_len,
      		hdr_len;
      	union {
      		__wsum          csum;
      		struct {
      			__u16   csum_start;
      			__u16   csum_offset;
      		};
      	};
      	__u32                   priority;
      	kmemcheck_bitfield_begin(flags1);
      	__u8                    RH_KABI_RENAME(local_df, ignore_df) :1,
      	cloned : 1,
      		 ip_summed : 2,
      				 nohdr : 1,
      					 nfctinfo : 3;
      	__u8                    pkt_type : 3,
      	fclone : 2,
      		 ipvs_property : 1,
      					 peeked : 1,
      						  nf_trace : 1;
      	kmemcheck_bitfield_end(flags1);
      	__be16                  protocol;
      
      	void(*destructor)(struct sk_buff *skb);
      #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
      	struct nf_conntrack     *nfct;
      #endif
      #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
      	struct nf_bridge_info   *nf_bridge;
      #endif
      
      	/* fields enclosed in headers_start/headers_end are copied
      	* using a single memcpy() in __copy_skb_header()
      	*/
      	/* private: */
      	RH_KABI_EXTEND(__u32    headers_start[0])
      		/* public: */
      
      		int                     skb_iif;
      
      	RH_KABI_REPLACE(__u32   rxhash,
      		__u32   hash)
      
      		__be16                  vlan_proto;
      	__u16                   vlan_tci;
      
      #ifdef CONFIG_NET_SCHED
      	__u16                   tc_index;       /* traffic control index */
      #ifdef CONFIG_NET_CLS_ACT
      	__u16                   tc_verd;        /* traffic control verdict */
      #endif
      #endif
      
      	__u16                   queue_mapping;
      	kmemcheck_bitfield_begin(flags2);
      #ifdef CONFIG_IPV6_NDISC_NODETYPE
      	__u8                    ndisc_nodetype : 2;
      #endif
      	__u8                    pfmemalloc : 1;
      	__u8                    ooo_okay : 1;
      	__u8                    RH_KABI_RENAME(l4_rxhash, l4_hash) :1;
      	__u8                    wifi_acked_valid : 1;
      	__u8                    wifi_acked : 1;
      	__u8                    no_fcs : 1;
      	__u8                    head_frag : 1;
      	/* Indicates the inner headers are valid in the skbuff. */
      	__u8                    encapsulation : 1;
      	RH_KABI_EXTEND(__u8                     encap_hdr_csum : 1)
      		RH_KABI_EXTEND(__u8                     csum_valid : 1)
      		RH_KABI_EXTEND(__u8                     csum_complete_sw : 1)
      		RH_KABI_EXTEND(__u8                     xmit_more : 1)
      		RH_KABI_EXTEND(__u8                     inner_protocol_type : 1)
      		RH_KABI_EXTEND(__u8                     remcsum_offload : 1)
      		/* 0/2 bit hole (depending on ndisc_nodetype presence) */
      		kmemcheck_bitfield_end(flags2);
      
      #if defined CONFIG_NET_DMA_RH_KABI || defined CONFIG_NET_RX_BUSY_POLL || defined CONFIG_XPS
      	union {
      		unsigned int    napi_id;
      		RH_KABI_EXTEND(unsigned int     sender_cpu)
      			RH_KABI_DEPRECATE(dma_cookie_t, dma_cookie)
      	};
      #endif
      #ifdef CONFIG_NETWORK_SECMARK
      	__u32                   secmark;
      #endif
      	union {
      		__u32           mark;
      		__u32           dropcount;
      		__u32           reserved_tailroom;
      	};
      
      #ifdef __GENKSYMS__
      	__be16                  inner_protocol;
      #else
      	union {
      		__be16          inner_protocol;
      		__u8            inner_ipproto;
      	};
      #endif
      
      	__u16                   inner_transport_header;
      	__u16                   inner_network_header;
      	__u16                   inner_mac_header;
      	__u16                   transport_header;
      	__u16                   network_header;
      	__u16                   mac_header;
      
      	RH_KABI_EXTEND(kmemcheck_bitfield_begin(flags3))
      		RH_KABI_EXTEND(__u8     csum_level : 2)
      		RH_KABI_EXTEND(__u8     rh_csum_pad : 1)
      		RH_KABI_EXTEND(__u8     rh_csum_bad_unused : 1) /* one bit hole */
      		RH_KABI_EXTEND(__u8     offload_fwd_mark : 1)
      		RH_KABI_EXTEND(__u8     sw_hash : 1)
      		RH_KABI_EXTEND(__u8     csum_not_inet : 1)
      		RH_KABI_EXTEND(__u8     dst_pending_confirm : 1)
      		RH_KABI_EXTEND(__u8     offload_mr_fwd_mark : 1)
      		/* 7 bit hole */
      		RH_KABI_EXTEND(kmemcheck_bitfield_end(flags3))
      
      		/* private: */
      		RH_KABI_EXTEND(__u32    headers_end[0])
      		/* public: */
      
      		/* RHEL SPECIFIC
      		*
      		* The following padding has been inserted before ABI freeze to
      		* allow extending the structure while preserve ABI. Feel free
      		* to replace reserved slots with required structure field
      		* additions of your backport, eventually moving the replaced slot
      		* before headers_end, if it need to be copied by __copy_skb_header()
      		*/
      		u32                     rh_reserved1;
      	u32                     rh_reserved2;
      	u32                     rh_reserved3;
      	u32                     rh_reserved4;
      	union {
      		unsigned int    napi_id;
      		RH_KABI_EXTEND(unsigned int     sender_cpu)
      			RH_KABI_DEPRECATE(dma_cookie_t, dma_cookie)
      	};
      #endif
      #ifdef CONFIG_NETWORK_SECMARK
      	__u32                   secmark;
      #endif
      	union {
      		__u32           mark;
      		__u32           dropcount;
      		__u32           reserved_tailroom;
      	};
      
      #ifdef __GENKSYMS__
      	__be16                  inner_protocol;
      #else
      	kmemcheck_bitfield_begin(flags1);
      	__u8                    RH_KABI_RENAME(local_df, ignore_df) :1,
      	cloned : 1,
      		 ip_summed : 2,
      				 nohdr : 1,
      					 nfctinfo : 3;
      	__u8                    pkt_type : 3,
      	fclone : 2,
      		 ipvs_property : 1,
      					 peeked : 1,
      						  nf_trace : 1;
      	kmemcheck_bitfield_end(flags1);
      	__be16                  protocol;
      
      	void(*destructor)(struct sk_buff *skb);
      #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
      	struct nf_conntrack     *nfct;
      #endif
      #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
      	struct nf_bridge_info   *nf_bridge;
      #endif
      
      	/* fields enclosed in headers_start/headers_end are copied
      	* using a single memcpy() in __copy_skb_header()
      	*/
      	/* private: */
      	/* private: */
      	RH_KABI_EXTEND(__u32    headers_start[0])
      		/* public: */
      
      		int                     skb_iif;
      
      	RH_KABI_REPLACE(__u32   rxhash,
      		__u32   hash)
      
      
      		__be16                  vlan_proto;
      	__u16                   vlan_tci;
      
      #ifdef CONFIG_NET_SCHED
      	__u16                   tc_index;       /* traffic control index */
      #ifdef CONFIG_NET_CLS_ACT
      	__u16                   tc_verd;        /* traffic control verdict */
      #endif
      #endif
      
      	__u16                   queue_mapping;
      	kmemcheck_bitfield_begin(flags2);
      #ifdef CONFIG_IPV6_NDISC_NODETYPE
      	__u8                    ndisc_nodetype : 2;
      #endif
      	__u8                    pfmemalloc : 1;
      	__u8                    ooo_okay : 1;
      	__u8                    RH_KABI_RENAME(l4_rxhash, l4_hash) :1;
      	__u8                    wifi_acked_valid : 1;
      	__u8                    wifi_acked : 1;
      	__u8                    no_fcs : 1;
      	__u8                    head_frag : 1;
      	/* Indicates the inner headers are valid in the skbuff. */
      	__u8                    encapsulation : 1;
      	RH_KABI_EXTEND(__u8                     encap_hdr_csum : 1)
      		RH_KABI_EXTEND(__u8                     csum_valid : 1)
      		RH_KABI_EXTEND(__u8                     csum_valid : 1)
      		RH_KABI_EXTEND(__u8                     csum_complete_sw : 1)
      		RH_KABI_EXTEND(__u8                     xmit_more : 1)
      		RH_KABI_EXTEND(__u8                     inner_protocol_type : 1)
      		RH_KABI_EXTEND(__u8                     remcsum_offload : 1)
      		/* 0/2 bit hole (depending on ndisc_nodetype presence) */
      		kmemcheck_bitfield_end(flags2);
      
      #if defined CONFIG_NET_DMA_RH_KABI || defined CONFIG_NET_RX_BUSY_POLL || defined CONFIG_XPS
      	union {
      		unsigned int    napi_id;
      		RH_KABI_EXTEND(unsigned int     sender_cpu)
      			RH_KABI_DEPRECATE(dma_cookie_t, dma_cookie)
      	};
      #endif
      #ifdef CONFIG_NETWORK_SECMARK
      	__u32                   secmark;
      #endif
      	union {
      		__u32           mark;
      		__u32           dropcount;
      		__u32           reserved_tailroom;
      	};
      
      #ifdef __GENKSYMS__
      	__be16                  inner_protocol;
      #else
      	union {
      		__be16          inner_protocol;
      		__u8            inner_ipproto;
      	};
      #endif
      
      	__u16                   inner_transport_header;
      	__u16                   inner_network_header;
      	__u16                   inner_mac_header;
      	__u16                   transport_header;
      	__u16                   network_header;
      	__u16                   mac_header;
      
      	RH_KABI_EXTEND(kmemcheck_bitfield_begin(flags3))
      		RH_KABI_EXTEND(__u8     csum_level : 2)
      		RH_KABI_EXTEND(__u8     rh_csum_pad : 1)
      		RH_KABI_EXTEND(__u8     rh_csum_bad_unused : 1) /* one bit hole */
      		RH_KABI_EXTEND(__u8     offload_fwd_mark : 1)
      		RH_KABI_EXTEND(__u8     sw_hash : 1)
      		RH_KABI_EXTEND(__u8     csum_not_inet : 1)
      		RH_KABI_EXTEND(__u8     dst_pending_confirm : 1)
      		RH_KABI_EXTEND(__u8     offload_mr_fwd_mark : 1)
      		/* 7 bit hole */
      		RH_KABI_EXTEND(kmemcheck_bitfield_end(flags3))
      
      		/* private: */
      		RH_KABI_EXTEND(__u32    headers_end[0])
      		/* public: */
      
      		/* RHEL SPECIFIC
      		*
      		* The following padding has been inserted before ABI freeze to
      		* allow extending the structure while preserve ABI. Feel free
      		* to replace reserved slots with required structure field
      		* additions of your backport, eventually moving the replaced slot
      		* before headers_end, if it need to be copied by __copy_skb_header()
      		*/
      		u32                     rh_reserved1;
      	u32                     rh_reserved2;
      	u32                     rh_reserved3;
      	u32                     rh_reserved4;
      
      	/* These elements must be at the end, see alloc_skb() for details.  */
      	sk_buff_data_t          tail;
      	sk_buff_data_t          end;
      	unsigned char           *head,
      		*data;
      	unsigned int            truesize;
      	atomic_t                users;
      };
      

      什么是高效的IO?

      IO主要分为两步:

      • 第一步是等,即等待IO条件就绪。
      • 第二步是拷贝,也就是当IO条件就绪后将数据拷贝到内存或外设。

      任何IO的过程,都包含“等”和“拷贝”这两个步骤,但在实际的应用场景中“等”消耗的时间往往比“拷贝”消耗的时间多,因此要让IO变得高效,最核心的办法就是尽量减少“等”的时间。

      钓鱼五人组

      IO的过程其实和钓鱼是非常类似的。

      • 钓鱼的过程同样分为“等”和“拷贝”两个步骤,只不过这里的“等”指的是等鱼上钩,“拷贝”指的是当鱼上钩后将鱼从河里“拷贝”到我们的鱼桶当中。
      • IO时“等”消耗的时间往往比“拷贝”消耗的时间多,钓鱼也恰好符合这个特点,钓鱼时我们大部分时间都在等鱼上钩,而当鱼上钩后只需要一瞬间就能将鱼“拷贝”上来。

      Linux高级IO
      在谈论高效的IO之前,我们先来看看什么样的钓鱼方式才是高效的。

      下面给出五个人的钓鱼方式:

      • 张三:拿了1个鱼竿,将鱼钩抛入水中后就死死的盯着浮漂,什么也不做,当有鱼上钩后就挥动鱼竿将鱼钓上来。
      • 李四:拿了1个鱼竿,将鱼钩抛入水中后就去做其他事情,然后定期观察浮漂,如果有鱼上钩则挥动鱼竿将鱼钓上来,否则继续去做其他事情。
      • 王五:拿了1个鱼竿,将鱼钩抛入水中后在鱼竿顶部绑一个铃铛,然后就去做其他事情,如果铃铛响了就挥动鱼竿将鱼钓上来,否则就根本不管鱼竿。
      • 赵六:拿了100个鱼竿,将100个鱼竿抛入水中后就定期观察这100个鱼竿的浮漂,如果某个鱼竿有鱼上钩则挥动对应的鱼竿将鱼钓上来。
      • 田七:田七是一个有钱的老板,他给了自己的司机一个桶、一个电话、一个鱼竿,让司机去钓鱼,当鱼桶装满的时候再打电话告诉田七来拿鱼,而田七自己则开车去做其他事情去了。

      张三、李四、王五的钓鱼效率是否一样?为什么?

      张三、李四、王五的钓鱼效率本质上是一样的。

      • 首先他们的钓鱼方式都是一样的,都是先等鱼上钩,然后再将鱼钓上来。
      • 其次,因为他们每个人都是拿的一根鱼竿,当河里有鱼来咬鱼钩时,这条鱼咬哪一个鱼钩的概率都是相等的。

      因此张三、李四、王五他们三个人的钓鱼的效率是一样的,他们只是等鱼上钩的方式不同而已,张三是死等,李四是定期检测浮漂,而王五是通过铃铛来判断是否有鱼上钩。

      需要注意的是,这里问的是他们的钓鱼效率是否是一样的,而不是问他们整体谁做的事最多,如果说整体做事情的量的话,那一定是王五做得最多,李四次之,张三最少。

      张三、李四、王五它们三个人分别和赵六比较,谁的钓鱼效率更高?

      赵六毫无疑问是这四个人当中钓鱼效率最高的,因为赵六同时在等多个鱼竿上有鱼上钩,因此在单位时间内,赵六的鱼竿有鱼上钩的概率是最大的。

      • 为了方便计算,我们假设赵六拿了97个鱼竿,加上张三、李四、王五的鱼竿一共就有100个鱼竿。
      • 当河里有鱼来咬鱼钩时,这条鱼咬张三、李四、王五的鱼钩的概率都是百分之一,而咬赵六的鱼钩的概率就是百分之九十七。
      • 因此在单位时间内,赵六的鱼竿上有鱼的概率是张三、李四、王五的97倍。

      而高效的钓鱼就是要减少单位时间内“等”的时间,增加“拷贝”的时间,所以说赵六的钓鱼效率是这四个人当中最高的。

      赵六的钓鱼效率之所以高,是因为赵六一次等待多个鱼竿上的鱼上钩,此时就可以将“等”的时间进行重叠。

      如何看待田七的这种钓鱼方式?

      田七让自己的司机帮自己钓鱼,自己开车去做其他事情去了,此时这个司机具体怎么钓鱼已经不重要了,他可以模仿张三、李四、王五、赵六任何一个人的钓鱼方式进行钓鱼。

      最重要的是田七本人并没有参与整个钓鱼的过程,他只是发起了钓鱼的任务,而真正钓鱼的是司机,田七在司机钓鱼期间可能在做任何其他事情,如果将钓鱼看作是一种IO的话,那田七的这种钓鱼方式就叫做异步IO。

      而对于张三、李四、王五、赵六来说,他们都需要自己等鱼上钩,当鱼上钩后又需要自己把鱼从河里钓上来,对应到IO当中就是需要自己进行数据的拷贝,因此他们四个人的钓鱼方式都叫做同步IO。

      五种IO模型

      实际这五个人的钓鱼方式分别对应的就是五种IO模型。

      • 张三这种死等的钓鱼方式对应就是阻塞IO。
      • 李四这种定时检测是否有鱼上钩的方式就是非阻塞IO。
      • 王五这种通过设置铃铛得知事件是否就绪的方式就是信号驱动IO。
      • 王五这种一次等待多个鱼竿上有鱼的钓鱼方式就是IO多路转接。
      • 田七这种让别人帮自己钓鱼的钓鱼方式就是异步IO。

      通过这里的钓鱼例子我们可以看到,阻塞IO、非阻塞IO和信号驱动IO本质上是不能提高IO的效率的,但非阻塞IO和信号驱动IO能提高整体做事的效率。

      其中,这个钓鱼场景中的各个事物都能与IO当中的相关概念对应起来,比如这里钓鱼的河对应就是内核,这里的每一个人都是进程或线程,鱼竿对应的就是文件描述符或套接字,装鱼的桶对应的就是用户缓冲区。

      五种IO模型

      阻塞IO

      阻塞IO就是在内核将数据准备好之前,系统调用会一直等待。

      图示如下:
      Linux高级IO
      阻塞IO是最常见的IO模型,所有的套接字,默认都是阻塞方式。

      • 比如当调用recvfrom函数从某个套接字上读取数据时,可能底层数据还没有准备好,此时就需要等待数据就绪,当数据就绪后再将数据从内核拷贝到用户空间,最后recvfrom函数才会返回。
      • 在recvfrom函数等待数据就绪期间,在用户看来该进程或线程就阻塞住了,本质就是操作系统将该进程或线程的状态设置为了某种非R状态,然后将其放入等待队列当中,当数据就绪后操作系统再将其从等待队列当中唤醒,然后该进程或线程再将数据从内核拷贝到用户空间。

      以阻塞方式进行IO操作的进程或线程,在“等”和“拷贝”期间都不会返回,在用户看来就像是阻塞住了,因此我们称之为阻塞IO。

      非阻塞IO

      非阻塞IO就是,如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码。

      图示如下:
      Linux高级IO
      非阻塞IO往往需要程序员以循环的方式反复尝试读写文件描述符,这个过程称为轮询,这对CPU来说是较大的浪费,一般只有特定场景下才使用。

      • 比如当调用recvfrom函数以非阻塞方式从某个套接字上读取数据时,如果底层数据还没有准备好,那么recvfrom函数会立马错误返回,而不会让该进程或线程进行阻塞等待。
      • 因为没有读取的数据,因此该进程或线程后续还需要继续调用recvfrom函数,检测底层数据是否就绪,如果没有就绪则继续错误返回,直到某次检测到底层数据就绪后,再将数据从内核拷贝到用户空间然后进行成功返回。
      • 每次调用recvfrom函数读取数据时,就算底层数据没有就绪,recvfrom函数也会立马返回,在用户看来该进程或线程就没有被阻塞住,因此我们称之为非阻塞IO。

      阻塞IO和非阻塞IO的区别在于,阻塞IO当数据没有就绪时,后续检测数据是否就绪的工作是由操作系统发起的,而非阻塞IO当数据没有就绪时,后续检测数据是否就绪的工作是由用户发起的。

      信号驱动IO

      信号驱动IO就是当内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。

      图示如下:
      Linux高级IO
      当底层数据就绪的时候会向当前进程或线程递交SIGIO信号,因此可以通过signal或sigaction函数将SIGIO的信号处理程序自定义为需要进行的IO操作,当底层数据就绪时就会自动执行对应的IO操作。

      • 比如我们需要调用recvfrom函数从某个套接字上读取数据,那么就可以将该操作定义为SIGIO的信号处理程序。
      • 当底层数据就绪时,操作系统就会递交SIGIO信号,此时就会自动执行我们定义的信号处理程序,进程将数据从内核拷贝到用户空间。

      信号的产生是异步的,但信号驱动IO是同步IO的一种。

      • 我们说信号的产生异步的,因为信号在任何时刻都可能产生。
      • 但信号驱动IO是同步IO的一种,因为当底层数据就绪时,当前进程或线程需要停下正在做的事情,转而进行数据的拷贝操作,因此当前进程或线程仍然需要参与IO过程。

      判断一个IO过程是同步的还是异步的,本质就是看当前进程或线程是否需要参与IO过程,如果要参与那就是同步IO,否则就是异步IO。

      IO多路转接

      IO多路转接也叫做IO多路复用,能够同时等待多个文件描述符的就绪状态。

      图示如下:
      Linux高级IO
      IO多路转接的思想:

      • 因为IO过程分为“等”和“拷贝”两个步骤,因此我们使用的recvfrom等接口的底层实际上都做了两件事,第一件事就是当数据不就绪时需要等,第二件事就是当数据就绪后需要进行拷贝。
      • 虽然recvfrom等接口也有“等”的能力,但这些接口一次只能“等”一个文件描述符上的数据或空间就绪,这样IO效率太低了。
      • 因此系统为我们提供了三组接口,分别叫做select、poll和epoll,这些接口的核心工作就是“等”,我们可以将所有“等”的工作都交给这些多路转接接口。
      • 因为这些多路转接接口是一次“等”多个文件描述符的,因此能够将“等”的时间进行重叠,当数据就绪后再调用对应的recvfrom等函数进行数据的拷贝,此时这些函数就能够直接进行拷贝,而不需要进行“等”操作了。

      IO多路转接就像现实生活中的黄牛一样,只不过IO多路转接更像是帮人排队的黄牛,因为多路转接接口实际并没有帮我们进行数据拷贝的操作。这些排队黄牛可以一次帮多个人排队,此时就将多个人排队的时间进行了重叠。

      异步IO

      异步IO就是由内核在数据拷贝完成时,通知应用程序。

      图示如下:
      Linux高级IO

      • 进行异步IO需要调用一些异步IO的接口,异步IO接口调用后会立马返回,因为异步IO不需要你进行“等”和“拷贝”的操作,这两个动作都由操作系统来完成,你要做的只是发起IO。
      • 当IO完成后操作系统会通知应用程序,因此进行异步IO的进程或线程并不参与IO的所有细节。

      高级IO重要概念

      同步通信 VS 异步通信

      同步和异步关注的是消息通信机制。

      • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回,但是一旦调用返回,就得到返回值了;换句话说,就是由调用者主动等待这个调用的结果。
      • 异步则是相反,调用在发出之后,这个调用就直接返回了,所有没有返回结果;换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

      为什么非阻塞IO在没有得到结果之前就返回了?

      • IO是分为“等”和“拷贝”两步的,当调用recvfrom进行非阻塞IO时,如果数据没有就绪,那么调用会直接返回,此时这个调用返回时并没有完成一个完整的IO过程,即便调用返回了那也是属于错误的返回。
      • 因此该进程或线程后续还需要继续调用recvfrom,轮询检测数据是否就绪,当数据就绪后最后再把数据从内核拷贝到用户空间,这才是一次完整的IO过程。

      因此,在进行非阻塞IO时,在没有得到结果之前,虽然这个调用会返回,但后续还需要继续进行轮询检测,因此可以理解成调用还没有返回,而只有当某次轮询检测到数据就绪,并且完成数据拷贝后才认为该调用返回了。

      同步通信 VS 同步与互斥

      在多进程和多线程当中有同步与互斥的概念,但是这里的同步通信和进程或线程之间的同步是完全不相干的概念。

      • 进程/线程同步指的是,在保证数据安全的前提下,让进程/线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,谈论的是进程/线程间的一种工作关系。
      • 而同步IO指的是进程/线程与操作系统之间的关系,谈论的是进程/线程是否需要主动参与IO过程。

      因此当看到“同步”这个词的时候,一定要先明确这个同步是同步通信的同步,还是同步与互斥的同步。

      阻塞 VS 非阻塞

      阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态。

      • 阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。
      • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

      其他高级IO

      非阻塞IO,记录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射IO(mmap),这些统称为高级IO。

      阻塞IO

      系统中大部分的接口都是阻塞式接口,比如我们可以用read函数从标准输入当中读取数据。

      #include <iostream>
      #include <unistd.h>
      #include <fcntl.h>
      
      int main()
      {
      	char buffer[1024];
      	while (true){
      		ssize_t size = read(0, buffer, sizeof(buffer)-1);
      		if (size < 0){
      			std::cerr << "read error" << std::endl;
      			break;
      		}
      		buffer[size] = '\0';
      		std::cout << "echo# " << buffer << std::endl;
      	}
      	return 0;
      }
      

      程序运行后,如果我们不进行输入操作,此时该进程就会阻塞住,根本原因就是因为此时底层数据不就绪,因此read函数需要进行阻塞等待。
      Linux高级IO
      一旦我们进行了输入操作,此时read函数就会检测到底层数据就绪,然后立马将数据读取到从内核拷贝到我们传入的buffer数组当中,并且将读取到的数据输出到显示器上面,最后我们就看到了我们输入的字符串。
      Linux高级IO
      说明一下:

      • C++当中的cin和C语言当中的scanf也可以读取从键盘输入的字符,但是cin和scanf会提供用户缓冲区,为了避免这些因素的干扰,因此这里选择使用read函数进行读取。

      非阻塞IO

      打开文件时默认都是以阻塞的方式打开的,如果要以非阻塞的方式打开某个文件,需要在使用open函数打开文件时携带O_NONBLOCK或O_NDELAY选项,此时就能够以非阻塞的方式打开文件。
      Linux高级IO
      这是在打开文件时设置非阻塞的方式,如果要将已经打开的某个文件或套接字设置为非阻塞,此时就需要用到fcntl函数。

      fcntl函数

      fcntl函数的函数原型如下:

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

      参数说明:

      • fd:已经打开的文件描述符。
      • cmd:需要进行的操作。
      • …:可变参数,传入的cmd值不同,后面追加的参数也不同。

      fcntl函数常用的5种功能与其对应的cmd取值如下:

      • 复制一个现有的描述符(cmd=F_DUPFD)。
      • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)。
      • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)。
      • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)。
      • 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)。

      返回值说明:

      • 如果函数调用成功,则返回值取决于具体进行的操作。
      • 如果函数调用失败,则返回-1,同时错误码会被设置。

      实现SetNonBlock函数

      我们可以定义一个函数,该函数就用于将指定的文件描述符设置为非阻塞状态。

      • 先调用fcntl函数获取该文件描述符对应的文件状态标记(这是一个位图),此时调用fcntl函数时传入的cmd值为F_GETFL。
      • 在获取到的文件状态标记上添加非阻塞标记O_NONBLOCK,再次调用fcntl函数对文件状态标记进行设置,此时调用fcntl函数时传入的cmd值为F_SETFL。

      代码如下:

      bool SetNonBlock(int fd)
      {
      	int fl = fcntl(fd, F_GETFL);
      	if (fl < 0){
      		std::cerr << "fcntl error" << std::endl;
      		return false;
      	}
      	fcntl(fd, F_SETFL, fl | O_NONBLOCK);
      	return true;
      }
      

      此时就将该文件描述符设置为了非阻塞状态。

      以非阻塞轮询方式读取标准输入

      此时在调用read函数读取标准输入之前,调用SetNonBlock函数将0号文件描述符设置为非阻塞就行了。

      代码如下:

      #include <iostream>
      #include <unistd.h>
      #include <fcntl.h>
      #include <signal.h>
      #include <cstring>
      #include <cerrno>
      bool SetNonBlock(int fd)
      {
      	int fl = fcntl(fd, F_GETFL);
      	if (fl < 0){
      		std::cerr << "fcntl error" << std::endl;
      		return false;
      	}
      	fcntl(fd, F_SETFL, fl | O_NONBLOCK);
      	return true;
      }
      int main()
      {
      	SetNonBlock(0);
      	char buffer[1024];
      	while (true){
      		ssize_t size = read(0, buffer, sizeof(buffer)-1);
      		if (size < 0){
      			if (errno == EAGAIN || errno == EWOULDBLOCK){ //底层数据没有就绪
      				std::cout << strerror(errno) << std::endl;
      				sleep(1);
      				continue;
      			}
      			else if (errno == EINTR){ //在读取数据之前被信号中断
      				std::cout << strerror(errno) << std::endl;
      				sleep(1);
      				continue;
      			}
      			else{
      				std::cerr << "read error" << std::endl;
      				break;
      			}
      		}
      		buffer[size] = '\0';
      		std::cout << "echo# " << buffer << std::endl;
      	}
      	return 0;
      }
      

      需要注意的是,当read函数以非阻塞方式读取标准输入时,如果底层数据不就绪,那么read函数就会立即返回,但当底层数据不就绪时,read函数是以出错的形式返回的,此时的错误码会被设置为EAGAIN或EWOULDBLOCK。
      Linux高级IO
      因此在以非阻塞方式读取数据时,如果调用read函数时得到的返回值是-1,此时还需要通过错误码进一步进行判断,如果错误码的值是EAGAIN或EWOULDBLOCK,说明本次调用read函数出错是因为底层数据还没有就绪,因此后续还应该继续调用read函数进行轮询检测数据是否就绪,当数据继续时再进行数据的读取。

      此外,调用read函数在读取到数据之前可能会被其他信号中断,此时read函数也会以出错的形式返回,此时的错误码会被设置为EINTR,此时应该重新执行read函数进行数据的读取。
      Linux高级IO
      因此在以非阻塞的方式读取数据时,如果调用read函数读取到的返回值为-1,此时并不应该直接认为read函数在底层读取数据时出错了,而应该继续判断错误码,如果错误码的值为EAGAIN、EWOULDBLOCK或EINTR则应该继续调用read函数再次进行读取。

      运行代码后,当我们没有输入数据时,程序就会不断调用read函数检测底层数据是否就绪。
      Linux高级IO
      一旦我们进行了输入操作,此时read函数就会在轮询检测时检测到,紧接着立马将数据读取到从内核拷贝到我们传入的buffer数组当中,并且将读取到的数据输出到显示器上面。
      Linux高级IO

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.csdn.net/chenlong_cxy/article/details/126050039,作者:2021dragon,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:IDEA常用快捷键

      下一篇:提高企业产品交付效率系列(1)—— 企业应用一键安装和升级

      相关文章

      2025-05-19 09:04:53

      查看RISC-V版本的gcc中默认定义的宏

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53
      c++ , linux
      2025-05-14 09:51:21

      Docker大学生看了都会系列(十、Docker网络)

      docker使用Linux桥接网卡,在宿主机虚拟一个docker容器网桥(docker0),docker启动一个容器时会根据docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网络网关。

      2025-05-14 09:51:21
      docker , Docker , 容器 , 宿主机 , 模式 , 网桥 , 网络
      2025-05-06 09:19:39

      【30天玩转python】网络编程基础

      网络编程是指编写能够在网络上进行通信的程序,通过网络进行数据的发送与接收。Python 提供了许多库和工具来进行网络编程,如 socket、urllib 和 requests。

      2025-05-06 09:19:39
      TCP , 地址 , 端口 , 编程 , 网络
      2025-05-06 09:18:49

      【Linux 从基础到进阶】Docker 网络配置与调优

      Docker 提供了强大的网络功能,使得容器之间、容器与宿主机、容器与外部网络之间的通信变得高效而灵活。理解和优化 Docker 网络配置对于确保容器应用的性能和可靠性至关重要。

      2025-05-06 09:18:49
      Docker , 容器 , 宿主机 , 网络
      2025-04-01 10:28:37

      小课2:筛选信息命令

      小课2:筛选信息命令

      2025-04-01 10:28:37
      bash , linux , 升序 , 服务器 , 运维
      2025-03-28 07:42:20

      知识图谱的演化:从语义网络到智能决策的关键

      知识图谱的演化:从语义网络到智能决策的关键

      2025-03-28 07:42:20
      图谱 , 数据 , 知识 , 网络 , 语义
      2025-03-27 09:41:50

      初识aurora

      初识aurora

      2025-03-27 09:41:50
      其他 , 笔记 , 网络
      2025-03-26 09:31:12

      shell脚本实现查询代码中定义了多少宏的方法

      shell脚本实现查询代码中定义了多少宏的方法

      2025-03-26 09:31:12
      bash , linux , 运维
      2025-03-26 09:05:38

      Docker网络模型深度解析

      Docker 的网络模型是 Docker 容器之间以及与宿主机之间进行通信的基础。Docker 提供了几种不同的网络模式,包括 bridge(桥接模式,默认模式)、host(主机模式)、none(无网络模式)、container(容器模式)以及自定义网络(如 overlay 网络,用于 Docker Swarm)。这里,我们将详细解析 bridge 网络模式,并提供一个实际的代码示例,展示如何创建容器并让它们在同一桥接网络上通信。

      2025-03-26 09:05:38
      Docker , 创建 , 容器 , 网络
      2025-03-21 06:57:11

      课程介绍,基础—环境安装、判断、循环语句等(爬虫及数据可视化)

      课程介绍,基础—环境安装、判断、循环语句等(爬虫及数据可视化)

      2025-03-21 06:57:11
      python , 关键字 , 可视化 , 爬虫 , 网络 , 语句 , 运算符
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5233338

      查看更多

      最新文章

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53

      Docker大学生看了都会系列(十、Docker网络)

      2025-05-14 09:51:21

      docker之容器网络篇

      2025-03-17 08:27:30

      【网关】什么是网关、网桥和交换机(集线器、网桥、交换机、路由器、网关大解析)

      2025-02-21 08:57:46

      vlan划分详解

      2024-12-20 07:55:40

      【云原生技术】Docker基础知识-docker0网桥

      2024-12-13 06:53:39

      查看更多

      热门文章

      Linux crontab 任务误删恢复及备份步骤

      2023-03-20 08:19:07

      Linux 趣味小知识--软硬连接以及应用

      2023-04-23 09:32:49

      Linux常用命令总结

      2023-05-12 07:20:42

      linux-压缩与解压缩

      2023-05-15 10:03:24

      linux基本命令(47)——iostat命令

      2023-05-12 07:21:43

      Linux中文本搜索命令grep用法详解

      2023-06-07 07:36:41

      查看更多

      热门标签

      linux java python javascript 数组 前端 docker Linux vue 函数 shell git 节点 容器 示例
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      Linux的chattr与lsattr命令介绍

      Linux命令之修改文件所有者chown

      Linux安装Telnet

      linux 网络命令

      ARP渗透与攻防之ARP原理

      Docker的四种网络类型?

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号