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

用 C 语言函数表实现通信传输层抽象

用 C 语言函数表实现通信传输层抽象在嵌入式 Linux 或工业控制类程序中一个应用经常需要同时接入多种通信链路例如 UDP、串口、CAN、TCP 或 Unix Socket。这些链路的底层实现差异很大UDP 基于 socket串口基于 tty 设备CAN 基于 SocketCAN不同链路的打开、关闭、收发、文件描述符获取方式都不同如果上层业务直接调用这些底层接口代码很容易变得混乱。业务逻辑中会到处出现sendto、read、write、ioctl、recvfrom等底层调用。一旦增加新的链路或者替换某个链路实现就可能牵动大量业务代码。一种更稳妥的方式是用 C 语言函数表抽象通信传输层再用select在单线程中统一监听多个输入源。1. 为什么要抽象传输层对上层业务来说它通常并不关心底层是 UDP、串口还是 CAN。它真正需要的能力只有几类open();close();send();recv();get_fd();is_open();也就是说上层只关心“这个通道能不能打开、能不能收发、能不能被事件循环监听”。因此可以定义一个统一的传输接口typedefstructtransporttransport_t;typedefstruct{int(*open)(transport_t*t);void(*close)(transport_t*t);int(*send)(transport_t*t,constuint8_t*data,uint16_tlen);int(*recv)(transport_t*t,uint8_t*buf,uint16_tlen,uint32_ttimeout_ms);int(*get_fd)(transport_t*t);int(*is_open)(transport_t*t);}transport_ops_t;structtransport{consttransport_ops_t*ops;void*ctx;};这里的transport_ops_t是一张函数表。它的作用类似面向对象语言里的接口transport_t表示一个抽象传输对象ops表示这个对象支持哪些操作ctx保存具体实现的数据例如 UDP socket、串口 fd、CAN socket 等上层只拿到transport_t *通过t-ops-send()发送数据通过t-ops-recv()接收数据不需要知道底层实现细节。2. 如何把不同设备统一成 transport_t以 UDP 为例可以定义一个具体结构体typedefstruct{transport_tbase;intfd;charremote_ip[32];uint16_tremote_port;intopened;}udp_transport_t;然后实现 UDP 对应的操作函数staticintudp_open(transport_t*t){udp_transport_t*u(udp_transport_t*)t-ctx;u-fdsocket(AF_INET,SOCK_DGRAM,0);if(u-fd0){return-1;}u-opened1;return0;}staticvoidudp_close(transport_t*t){udp_transport_t*u(udp_transport_t*)t-ctx;if(u-opened){close(u-fd);u-opened0;}}staticintudp_send(transport_t*t,constuint8_t*data,uint16_tlen){udp_transport_t*u(udp_transport_t*)t-ctx;returnsend(u-fd,data,len,0);}staticintudp_recv(transport_t*t,uint8_t*buf,uint16_tlen,uint32_ttimeout_ms){udp_transport_t*u(udp_transport_t*)t-ctx;(void)timeout_ms;returnrecv(u-fd,buf,len,0);}staticintudp_get_fd(transport_t*t){udp_transport_t*u(udp_transport_t*)t-ctx;returnu-fd;}staticintudp_is_open(transport_t*t){udp_transport_t*u(udp_transport_t*)t-ctx;returnu-opened;}再把这些函数组装成一张操作表staticconsttransport_ops_tudp_ops{.openudp_open,.closeudp_close,.sendudp_send,.recvudp_recv,.get_fdudp_get_fd,.is_openudp_is_open};初始化时把具体对象和抽象接口绑定起来voidudp_transport_init(udp_transport_t*u){memset(u,0,sizeof(*u));u-base.opsudp_ops;u-base.ctxu;}transport_t*udp_transport_as_transport(udp_transport_t*u){returnu-base;}这样UDP 就被包装成了一个transport_t。串口、CAN 也可以用同样的方法包装transport_t*udpudp_transport_as_transport(udp_obj);transport_t*serialserial_transport_as_transport(serial_obj);transport_t*cancan_transport_as_transport(can_obj);上层看到的都是transport_t *不会再直接依赖 UDP、串口或 CAN 的具体类型。3. 上层如何避免关心底层类型有了统一接口后上层发送数据可以写成这样intchannel_send(transport_t*t,constuint8_t*data,uint16_tlen){if(!t||!t-ops||!t-ops-send){return-1;}if(t-ops-is_open!t-ops-is_open(t)){return-1;}returnt-ops-send(t,data,len);}接收数据也可以统一intchannel_recv(transport_t*t,uint8_t*buf,uint16_tlen){if(!t||!t-ops||!t-ops-recv){return-1;}returnt-ops-recv(t,buf,len,0);}如果以后新增 TCP只需要实现一组tcp_ops并把 TCP 包装成transport_t。业务层的收发逻辑不需要修改。这就是函数指针表的价值把变化隔离在底层把稳定接口暴露给上层。基于 select 的单线程事件循环设计如果每个链路开一个线程当然也能工作。但线程多了以后会带来新的复杂度线程同步共享数据加锁退出流程复杂调试困难时序问题更隐蔽如果通信压力不大或者业务逻辑适合串行处理那么单线程事件循环是很好的选择。核心思路是从每个transport_t里取出 fd加入fd_set调用select哪个 fd 可读就读取哪个通道然后统一做协议解析和业务处理4. 用 select 同时监听多个链路示例代码voidevent_loop(transport_t**channels,size_tcount){while(1){fd_set rfds;intmaxfd-1;FD_ZERO(rfds);for(size_ti0;icount;i){transport_t*tchannels[i];if(!t||!t-ops||!t-ops-get_fd){continue;}intfdt-ops-get_fd(t);if(fd0){continue;}FD_SET(fd,rfds);if(fdmaxfd){maxfdfd;}}structtimevaltv;tv.tv_sec0;tv.tv_usec50*1000;intretselect(maxfd1,rfds,NULL,NULL,tv);if(ret0){for(size_ti0;icount;i){transport_t*tchannels[i];if(!t||!t-ops||!t-ops-get_fd){continue;}intfdt-ops-get_fd(t);if(fd0FD_ISSET(fd,rfds)){uint8_tbuf[1500];intnt-ops-recv(t,buf,sizeof(buf),0);if(n0){handle_input(t,buf,(size_t)n);}}}}elseif(ret0){/* * 实际工程中要处理 EINTR。 * 如果只是被信号中断不一定是致命错误。 */}poll_periodic_tasks();}}这里有一个关键点select的 timeout 不只是等待 IO它还决定了周期任务的调度粒度。例如 timeout 设置为 50ms那么即使没有任何 IO 输入主循环最多也会在 50ms 后醒来一次检查定时任务是否到期。5. 周期任务如何放进事件循环很多程序除了接收数据还需要周期性执行任务例如心跳发送状态上报超时检查定期查询重发控制可以封装一个简单的周期定时器typedefstruct{uint64_tperiod_ms;uint64_tnext_ms;}periodic_timer_t;inttimer_expired(periodic_timer_t*timer,uint64_tnow_ms){if(now_mstimer-next_ms){return0;}timer-next_msnow_mstimer-period_ms;return1;}在主循环里使用periodic_timer_theartbeat_timer{.period_ms1000,.next_msnow_ms()1000};periodic_timer_tstatus_timer{.period_ms200,.next_msnow_ms()200};while(1){uint64_tnownow_ms();if(timer_expired(heartbeat_timer,now)){send_heartbeat();}if(timer_expired(status_timer,now)){send_status();}run_select_once();decode_all_channels();}这种设计的好处是IO 事件和周期任务都在一个线程里处理不需要锁也不会出现多个线程同时修改同一份状态的问题。6. IO 接收、协议解析、业务处理如何拆层一个比较清晰的结构可以分成三层transport 层负责 open / close / send / recv channel 层负责缓存、协议解析、组帧 business 层负责处理解析后的业务消息不要在recv后立刻写复杂业务逻辑。更好的方式是intntransport_recv(...);if(n0){ring_buffer_write(channel-rb,buf,n);}protocol_feed(channel-codec,data,len);while(protocol_pop(channel-codec,frame)){message_parse(frame,msg);message_dispatch(msg);}这样做有几个好处transport只负责字节收发codec只负责帧同步和校验message_parse只负责业务字段解析message_dispatch只负责业务分发每一层都比较纯后期排查问题会轻松很多。7. timeout 怎么设置selecttimeout 太长会影响周期任务响应速度太短又会导致主循环频繁醒来增加 CPU 占用。一般可以按实时性要求选择1000ms适合低频后台任务 100ms 适合普通控制程序 50ms 比较常见的折中值 10ms 响应更快但 CPU 唤醒更频繁更精细的做法是动态计算 timeouttimeoutmin(next_timer_deadline-now,max_select_timeout);这样事件循环可以在最近一个定时任务到期前醒来而不是固定睡眠。但在很多嵌入式程序里固定 10ms、20ms、50ms 已经足够简单可靠。8. 单线程事件循环的优缺点优点结构简单没有锁竞争状态修改顺序清晰调试更容易适合 fd 数量不多、处理逻辑较快的程序缺点某个处理函数耗时太长会阻塞整个循环不能充分利用多核fd 数量很多时select不如epollselect有FD_SETSIZE限制所有业务都需要避免长时间阻塞所以单线程事件循环适合这种场景链路数量有限 单次业务处理很快 状态一致性要求高如果连接数很多或者业务处理耗时较长可以考虑epollworker 线程池IO 线程 业务线程消息队列解耦9. 核心收益用函数表抽象传输层用select做单线程事件循环本质上解决了两个问题。第一屏蔽底层链路差异。上层不再关心 UDP、串口、CAN 各自怎么打开、怎么收发只面向统一的transport_t接口编程。第二集中管理 IO 和周期任务。所有 fd 的输入、周期定时器、协议解析、业务分发都在一个主循环中完成程序行为更可预测。最终结构可以概括为UDP transport \ Serial transport - channel - codec - message - dispatcher CAN transport / select event loop: 1. poll timers 2. wait readable fds 3. recv bytes 4. decode frames 5. dispatch messages这是一种非常适合嵌入式 Linux C 程序的工程结构简单、直接、可扩展也足够稳定。

相关文章:

用 C 语言函数表实现通信传输层抽象

用 C 语言函数表实现通信传输层抽象 在嵌入式 Linux 或工业控制类程序中,一个应用经常需要同时接入多种通信链路,例如 UDP、串口、CAN、TCP 或 Unix Socket。 这些链路的底层实现差异很大: UDP 基于 socket串口基于 tty 设备CAN 基于 SocketC…...

【光栅和蛇形误差扩散半色调】基于Floyd-Steinberg算法进行误差扩散半色调研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

JDspyder:京东自动化抢购解决方案的技术实现与实战指南

JDspyder:京东自动化抢购解决方案的技术实现与实战指南 【免费下载链接】JDspyder 京东预约&抢购脚本,可以自定义商品链接 项目地址: https://gitcode.com/gh_mirrors/jd/JDspyder 在电商秒杀和限量商品抢购的激烈竞争中,技术手段…...

MD源码#MDH5影视源码主题模版下载 苹果CMS V10版

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 MD源码#MDH5影视源码主题模版下载 苹果CMS V10版 一键部署版本,完美运营版本带采集规则模块 system/include.html–公共引用文件 system/header.html–头部文件 system/foo…...

Cursor AI代码助手:重塑IDE开发体验,从智能补全到项目级协作

1. 项目概述:当AI代码助手遇上IDE,Cursor如何重塑开发体验 如果你是一名开发者,最近一定在圈子里频繁听到“Cursor”这个名字。它不是一个全新的编程语言,也不是一个颠覆性的框架,但它却实实在在地在改变着许多人的编码…...

忘记加密压缩包密码?开源工具ArchivePasswordTestTool帮你轻松找回

忘记加密压缩包密码?开源工具ArchivePasswordTestTool帮你轻松找回 【免费下载链接】ArchivePasswordTestTool 利用7zip测试压缩包的功能 对加密压缩包进行自动化测试密码 项目地址: https://gitcode.com/gh_mirrors/ar/ArchivePasswordTestTool 你是否曾因忘…...

HDD与SSD存储技术演进:从产业变迁看成本容量比与分层存储实践

1. 硬盘驱动器产业的十字路口:一场迟来的告别十多年前,当我在实验室里第一次把玩一块2.5英寸的机械硬盘,惊叹于它能在方寸之间存储数十GB的数据时,绝不会想到,这个看似坚不可摧的存储基石,其背后的商业帝国…...

硬核手搓解析!进程-内核分析:命令行参数及环境变量,重构main()

目录 命令行参数与环境变量 命令行参数 vim下的main() 环境变量 环境变量的应用举例 查询环境变量 全部查询 针对名称查询(常用的方式) 环境变量的更改 配置环境变量 进程:命令行参数及环境变量的关系 结论 获取环境变量 ①get…...

工程师背包线缆管理实战:从Cord Hog到DIY收纳方案全解析

1. 项目概述:从“线缆地狱”到个人收纳方案的探索作为一名常年与各种开发板、调试器、电源适配器和数据线打交道的硬件工程师,我的背包简直就是个微缩版的电子实验室。每天通勤,包里除了笔记本电脑,必然塞满了USB线、串口线、JTAG…...

需求驱动设计:构建可追溯、高质量的FPGA/ASIC开发流程

1. 项目概述:为什么我们需要一场关于“需求驱动设计”的讨论?如果你是一名FPGA或ASIC的设计工程师、项目经理,或者正在向这个领域迈进,那么“项目延期”、“功能bug在流片前夜才被发现”、“需求变更导致架构推倒重来”这些场景&a…...

阿里云第一季营收416亿:EBITA为38亿 同比增57%

雷递网 乐天 5月13日阿里巴巴(美股代码:“baba”,港股代号:9988)今日发布2026年第一季度的财报。财报显示,阿里2026年第一季度营收为2433.8亿元(352.83亿美元),同比增长3…...

阿里从蚂蚁收到股息33亿:AI投入加大致后者年利润153亿 同比降60%

雷递网 乐天 5月13日阿里今日发布财报。财报披露,蚂蚁在2026年第一季度给阿里带来的投资收益为3.75亿(约5500万美元),较上年同期的17.63亿元下降78.7%。截至2026年3月31日,阿里对蚂蚁集团在全面摊薄基础上的股权为33%。…...

专利撰写难、公开不规范,patent-disclosure-skill:一站式专利公开技巧工具,搞定专利文书规范撰写难题

在知识产权越来越受重视的当下,不管是科研人员、技术开发者,还是企业知识产权相关从业者,在专利相关工作中,总会遇到各种各样的棘手问题。 很多人深耕技术研发,好不容易做出创新成果,可一到专利公开、文书梳…...

Windows平台终极PDF处理指南:Poppler工具集完整解决方案

Windows平台终极PDF处理指南:Poppler工具集完整解决方案 【免费下载链接】poppler-windows Download Poppler binaries packaged for Windows with dependencies 项目地址: https://gitcode.com/gh_mirrors/po/poppler-windows 还在为Windows系统上繁琐的PDF…...

阿里季报图解:营收2434亿 AI迎商业化拐点,模型及应用ARR年底破300亿,派息25亿美元

雷递网 雷建平 5月13日阿里巴巴(美股代码:“baba”,港股代号:9988)今日发布2026年第一季度的财报。财报显示,阿里2026年第一季度营收为2433.8亿元(352.83亿美元),同比增长…...

夏普鸿海合作破裂启示:跨文化并购中的技术控制与信任危机

1. 一场被寄予厚望的“联姻”为何走向破裂?2012年3月,当日本液晶面板巨头夏普宣布与全球最大电子代工企业鸿海(富士康)达成资本合作时,整个东亚电子产业圈都为之震动。这被视为一个标志性事件:一家以技术自…...

汽车电子架构演进:从分布式ECU到域控制器的技术变革与工程实践

1. 从一周新闻看汽车电子的演进脉络2012年8月的那一周,对于汽车电子行业来说,是平静水面下暗流涌动的一个缩影。当时,我正和几位在主机厂和Tier 1供应商工作的朋友频繁交流,大家普遍的感觉是,传统的汽车电子电气架构&a…...

增材制造如何破解光电子小批量定制化制造难题

1. 项目概述:一份被“雪藏”的产业复兴蓝图最近在整理行业资料时,我翻到了一篇2012年《EE Times》的老文章,标题叫《Seeing the light on optoelectronics manufacturing》。文章的核心观点很有意思,它批评了当时美国国家研究委员…...

深度拆解GPT-Realtime-2:从“能听会说”到“听懂人话”,靠的是什么?

请你想象这个场景: 你打电话订酒店,中途改主意3次,还接了另一个电话。AI全程没让你重复一句话。——这就是GPT-Realtime-2做到的事。三大模型,三类场景的精准切割OpenAI此次发布的核心策略是专业化分工:GPT-Realtime-2…...

如何用 setItem 与 getItem 规范地存取本地的字符串数据

localStorage的setItem和getItem仅支持字符串,存对象需JSON序列化,取值须判null并容错解析;键名应统一前缀,敏感数据慎存,大文本需评估容量。用 setItem 和 getItem 存取本地字符串数据,核心是确保数据类型…...

Ai小程序入门00-初识AI编程(小白入门:不懂代码也能做小程序?AI编程到底怎么玩)

Ai小程序入门00-初识AI编程(小白入门:不懂代码也能做小程序?AI编程到底怎么玩) 📌 文章简介:很多人都有一个"做个小程序赚钱"或"实现自己创意"的梦想,但往往被复杂的代码、繁琐的环境配置劝退。如今,AI 编程工具(如 Cursor、Claude 等)彻底改变…...

边缘AI推理芯片选型指南:从吞吐量到延迟的实战评估

1. 从数据中心到边缘:AI推理范式的根本性转变如果你正在为你的下一个AI项目选型硬件,尤其是在考虑将模型部署到摄像头、汽车或者医疗设备上,那么“边缘AI推理”这个词你一定不陌生。但很多人,包括一些经验丰富的工程师&#xff0c…...

物联网隐私工程:从数据生命周期到安全设计实践

1. 物联网隐私困境:一个被误解的工程问题每次和同行聊起物联网项目,大家最头疼的往往是协议选型、功耗优化或者成本控制。至于隐私?那通常是产品经理或者法务部门在项目后期才想起来要填的“合规表格”。我自己在早期做智能家居网关时也犯过同…...

资深工程师如何应对年龄增长带来的工作挑战:从照明优化到人体工学实践

1. 从一次生日派对说起:工程师的“年龄”与“视界”去年,我参加了一个在餐厅举办的50岁生日派对。餐厅的灯光有些昏暗,当菜单递过来时,除了我,桌上的每个人都掏出了手机,打开了LED手电筒。而在隔壁桌&#…...

HiveWE:现代化魔兽争霸III地图编辑器完全指南

HiveWE:现代化魔兽争霸III地图编辑器完全指南 【免费下载链接】HiveWE A Warcraft III world editor. 项目地址: https://gitcode.com/gh_mirrors/hi/HiveWE 还在为魔兽争霸III原版地图编辑器缓慢的加载速度和复杂的操作而烦恼吗?HiveWE作为一款专…...

AHB与APB总线桥接设计及SoC系统优化

1. AHB总线架构与APB桥接设计精要在复杂SoC设计中,AMBA总线作为ARM架构的核心互联标准,其AHB(Advanced High-performance Bus)与APB(Advanced Peripheral Bus)的协同工作直接影响系统性能。APB桥作为高低速…...

【限时决策窗口】ChatGPT Plus会员购买指南:避开3个高发误区,抓住GPT-4 Turbo+文件解析+自定义GPT三重红利期

更多请点击: https://intelliparadigm.com 第一章:ChatGPT Plus会员值不值得买 ChatGPT Plus 提供每月 $20 的订阅服务,主打 GPT-4 模型访问、高优先级响应队列、文件上传解析(PDF/CSV/TXT 等)及自定义 GPTs 功能。是…...

火山引擎AgentKit实战:从零构建企业级AI智能体应用

1. 从零到一:AgentKit代码工坊深度解析与实战指南如果你正在寻找一个能快速上手、功能强大的企业级AI Agent开发平台,那么火山引擎的AgentKit绝对值得你花时间深入研究。最近,我花了大量时间泡在它的官方代码示例仓库bytedance/agentkit-samp…...

从0到1构建DeepSeek抗注入能力:97.3%拦截率验证的5层LLM网关架构设计

更多请点击: https://intelliparadigm.com 第一章:从0到1构建DeepSeek抗注入能力:97.3%拦截率验证的5层LLM网关架构设计 为应对Prompt注入、越狱指令与上下文污染等高阶对抗攻击,我们设计并落地了一套轻量级、可插拔的5层LLM网关…...

嵌入式FPGA如何重塑MCU设计:从可编程I/O到硬件加速的范式变革

1. 微控制器的十字路口:成本困境与集成机遇作为一名在嵌入式领域摸爬滚打了十几年的工程师,我亲眼见证了微控制器(MCU)从简单的8位机发展到如今动辄数百兆赫兹主频、集成度惊人的复杂系统。但这些年,一个核心矛盾越来越…...