上周提到了 tun/tap 转发框架的数据通道结构和优化 tun/tap 转发性能优化,涉及 RingBuffer,packetization 等核心话题。我也给出了一定的数据结构以及处理逻辑,但竟然没有高尚的 epoll,本文说说它,因为它不适合。

epoll 作为 select,poll 的升级替代,它的优势在于 “大量描述符场景,主动通知 IO 事件而无需遍历查找 IO 事件”,这意味着在少量描述符场景,epoll 并无优势,反而增加复杂性,但复杂性并没什么大不了的,本文主要强调,epoll 本质上是在串行处理 I 和 O,这导致双向流量的严重性能问题。

万事皆有因,异步多路复用机制提供的能力是 “将对描述符的 I 和 O 同时复用到同一个线程中”,select,poll,epoll 本质上都是一回事,它们作为一个整体,适合做什么,不适合做什么,要搞清楚,而不能将它们看做不同的东西,因为这样一来,你很容易陷入 epoll 降维打击 select,进而万能无敌的陷阱。

epoll 擅长业务消息的分拣处理,仅分拣消息后交由专门的线程,或直接处理短消息,但对于 tun/tap 等构建的隧道上的持续流量,同一个 socket 的 recv,send 在同一个循环体中会导致半双工问题,且同一 socket 的 recv 和 send 间,以及不同 socket 的 recv/send 间串行会导致饥饿,这需要引入一个复杂的公平调度机制来解决。总而言之,这种非 “多路复用”问题,epoll 很难应对。

比如,epoll_wait 循环体中处理 POLLIN,POLLOUT 的 if 判断的位置会直接影响公平性,同时涉及 ET,LT,编码调度,若非如此,持续的 I 会饿死 O,反之亦然,而对于持续流量,将调度交给系统调度器何乐而不为。

对于 I 和 O,一心不可二用,持续的双向隧道流量需要的恰恰是解复用,即将同一个描述符的 I 和 O 解复用到不同线程,而不是复用,所以选型时第一要务就应该排除掉 select,poll,epoll,libevent 等异步多路复用技术,而为每一个 socket 简单地创建两个线程分别作阻塞 I 和 O 几乎是唯一选择,但这由于太简单而显得 low,展示不出自己运用复杂技术的能力,进而选择 epoll 等多路复用的错误技术,然后再陷入持续优化的深渊,早干嘛去了。

编程者使用 epoll 处理隧道倒不是都为了炫技,有些也属于拿着锤子找钉子。受大环境教化对产线工人产出效率的倡导,大多数编程者更熟悉高级框架和高级库的相关 API,底层的 thread_create 则早就抛到九霄云外去了,从不知或已遗忘了返朴归真的方法论。

对于高级 API,我的态度还是度量时间尺度,平衡你编码调试的时间和代码运行的时间,但前提是你一定要深入理解这个高级 API 的底层,它解决了什么问题,适合做什么,不适合做什么。调用高级 API 肯定增加了程序运行时间,多一个指令就多一个指令的时延,但直接使用底层 API 却对编程者有极高的要求,否则就会延迟代码发布和上线时间,同时增加维护和 bugfix 时间,要平衡这两者。

言归正传,既然不使用 epoll,选择了简单创建两个线程,就又涉及线程相关的高级技巧,可谓到处都是坑。

都知道线程比进程更轻量,鞋城更轻量,但为引出线程库,协程这些高级概念,线程创建,销毁的管理开销必须要被诟病,这似乎是引出一个新技术的惯例,于是就在编程者中形成一种新范式,即涉及服务器端的多线程,一定要用线程库,协程,就像涉及多个 socket 的 IO 一定要用 epoll 一样。进程,线程,协程的纠缠,与 select,poll,epoll 几乎无异。

但线程库,鞋城同样不适合 tun/tap 隧道。理由和态度依然是度量并平衡时间尺度。

类似 web 服务器 mpm,若采用多线程,为每一个简单的 request/response 花 80us 创建一个线程并随后在 100us 后花 30us 销毁,确实是一笔很昂贵的开销,线程池被提出解决该问题,资源池化的典型套路,但对于隧道,持续的双向流哪怕仅存活 1s,创建,销毁线程不到 200us 的开销都显得微不足道,为什么引入复杂性呢,多花几小时甚至更久的时间调试线程池,再算上维护和 bugfix 时间,创建多少个线程才能偿还,而你的代码在线上的生命周期甚至熬不到那么久。

总结一下,对于隧道,很简单,不用 epoll,线程池,协程,这些高尚货,要纯性能就别高尚,让高尚去诉诸软件工程和项目。

隧道场景远比服务器场景更简单,它只是在两个方向的固定分发,所以只要下面就够了:

void readtun_thread(void *arg)
{while(1) {block = dequeue(pool);len = read_tun(block);enqueue(tun2socket, block);}
}
void writesocket_thread(void *arg)
{while(1) {block = dequeue(tun2socket);len = write_socket(block); // 后续再 batch 优化enqueue(pool, block);}
}
void readsocket_thread(void *arg) {...}
void writetun_thread(void *arg) {...}

浙江温州皮鞋湿,下雨进水不会胖。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微前端架构常见框架

1. iframe 这里指的是每个微应用独立开发部署,通过 iframe 的方式将这些应用嵌入到父应用系统中,几乎所有微前端的框架最开始都考虑过 iframe,但最后都放弃,或者使用部分功能,原因主要有: url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。 UI 不同…

SQL Server更改日志模式:操作指南与最佳实践!

全文目录:开篇语**前言****摘要****概述:SQL Server 的日志模式****日志模式的作用****三种日志模式**1. **简单恢复模式(Simple)**2. **完整恢复模式(Full)**3. **大容量日志恢复模式(Bulk-Log…

git的工作使用中实际经验

老输入烦人的密码 每次我git pull的时候都要叫我输入三次烦人的密码,问了deepseek也没有尝试成功 出现 enter passphrase for key ‘~/.ssh/id_rsa’ 的原因: 在生成key的时候,没有注意,不小心设置了密码, 导致每次提交的时候都会提示要输入密码, 也就是上面的提示…

科技赋能,宁夏农业绘就塞上新“丰”景

在贺兰山的巍峨身影下,在黄河水的温柔滋养中,宁夏这片古老而神奇的土地,正借助农业科技的磅礴力量,实现从传统农耕到智慧农业的华丽转身,奏响一曲科技与自然和谐共生的壮丽乐章。一、数字农业:开启智慧种植…

imx6ull-驱动开发篇36——Linux 自带的 LED 灯驱动实验

在之前的文章里,我们掌握了无设备树和有设备树这两种 platform 驱动的开发方式。但实际上有现成的,Linux 内核的 LED 灯驱动采用 platform 框架,我们只需要按照要求在设备树文件中添加相应的 LED 节点即可。本讲内容,我们就来学习…

深度学习中主流激活函数的数学原理与PyTorch实现综述

1. 定义与作用什么是激活函数?激活函数有什么用?答:激活函数(Activation Function)是一种添加到人工神经网络中的函数,旨在帮助网络学习数据中的复杂模式。类似于人类大脑中基于神经元的模型,激…

Linux高效备份:rsync + inotify实时同步

一、rsync 简介 rsync(Remote Sync)是 Linux 系统下的数据镜像备份工具,支持本地复制、远程同步(通过 SSH 或 rsync 协议),是一个快速、安全、高效的增量备份工具。二、rsync 特性 支持镜像保存整个目录树和…

一种通过模板输出Docx的方法

起因在2个群里都有网友讨论这个问题,俺就写了一个最简单的例子。其实,我们经常遇到一些Docx的输出的需求,“用模板文件进行处理”是最简单的一个方法,如果想预览也简单 DevExpress 、Teleric 都可以,而且也支持 Web 、…

探索 List 的奥秘:自己动手写一个 STL List✨

📖引言大家好!今天我们要一起来揭开 C 中 list 容器的神秘面纱——不是直接用 STL,而是亲手实现一个简化版的 list!🎉你是不是曾经好奇过:list 是怎么做到高效插入和删除的?🔍迭代器…

mysql占用高内存排查与解决

mysql占用高内存排查-- 查看当前全局内存使用情况(需要启用 performance_schema) SELECT * FROM sys.memory_global_total; -- 查看总内存使用 SELECT * FROM sys.memory_global_by_current_bytes LIMIT 10; -- 按模块分类查看内存使用排行memory/perfor…

构建真正自动化知识工作的AI代理

引言:新一代生产力范式的黎明 自动化知识工作的人工智能代理(AI Agent),或称“智能体”,正迅速从理论构想演变为重塑各行各业生产力的核心引擎。这些AI代理被定义为能够感知环境、进行自主决策、动态规划、调用工具并持…

青少年机器人技术(四级)等级考试试卷-实操题(2021年12月)

更多内容和历年真题请查看网站:【试卷中心 -----> 电子学会 ----> 机器人技术 ----> 四级】 网站链接 青少年软件编程历年真题模拟题实时更新 青少年机器人技术(四级)等级考试试卷-实操题(2021年12月) …

最新短网址源码,防封。支持直连、跳转。 会员无广

最新短网址源码,防封。支持直连、跳转。 会员无广告1.可将长网址自动缩短为短网址,方便记忆和使用。2.短网址默认为临时有效,可付费升级为永久有效,接入支付后可自动完成,无需人工操作。3.系统支持设置图片/文字/跳转页…

缓存-变更事件捕捉、更新策略、本地缓存和热key问题

缓存-基础知识 熟悉计算机基础的同学们都知道,服务的存储大多是多层级的,呈现金字塔类型。通常来说本机存储比通过网络通信的外部存储更快(现在也不一定了,因为网络传输速度很快,至少可以比一些过时的本地存储设备速度…

报表工具DevExpress .NET Reports v25.1新版本亮点:AI驱动的扩展

DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表。 DevExpress Reporting控件日前正式发布了v25.1…

kubernetes中pod的管理及优化

目录 2 资源管理方式 2.1 命令式对象管理 2.2 资源类型 2.2.1 常用的资源类型 2.2.2 kubectl常见命令操作 2.3 基本命令示例 2.4 运行和调试命令示例 2.5 高级命令示例 3 pod简介 3.1 创建自主式pod(生产环境不推荐) 3.1.1 优缺点 3.1.2 创建…

解释一下,Linux,shell,Vmware,Ubuntu,以及Linux命令和shell命令的区别

Linux 操作系统概述Linux 是一种开源的类 Unix 操作系统内核,由 Linus Torvalds 于 1991 年首次发布。作为现代计算的基础设施之一,它具有以下核心特征:多用户多任务特性允许多个用户同时操作系统资源,而模块化设计使其能够适应从…

Windows 系统中,添加打印机主要有以下几种方式

在 Windows 系统中,添加打印机主要有以下几种方式,我将从最简单到最复杂为您详细介绍。 方法一:自动安装(推荐首选) 这是 Windows 10 和 Windows 11 中最简单、最现代的方法。系统会自动搜索网络(包括无线和有线网络)上可用的打印机并安装驱动程序。 操作步骤: 进入…

Mixture of Experts Guided by Gaussian Splatters Matters

Mixture of Experts Guided by Gaussian Splatters Matters: A new Approach to Weakly-Supervised Video Anomaly Detection ICCV2025 https://arxiv.org/pdf/2508.06318 https://github.com/snehashismajhi/GS-MoEAbstract 视频异常检测(VAD)是一项具有…

SeaTunnel Databend Sink Connector CDC 功能实现详解

Databend 是一个面向分析型工作负载优化的 OLAP 数据库,采用列式存储架构。在处理 CDC(Change Data Capture,变更数据捕获)场景时,如果直接执行单条的 UPDATE 和 DELETE 操作,会严重影响性能,无…