【实时Linux实战系列】实时数据流的网络传输

在实时系统中,数据流的实时传输是许多应用场景的核心需求之一。无论是工业自动化中的传感器数据、金融交易中的高频数据,还是多媒体应用中的视频流,都需要在严格的时间约束内完成数据的传输。实时数据流的传输不仅要求高吞吐量,还要求低延迟和高可靠性。掌握实时数据流的网络传输技能对于开发者来说至关重要,它可以帮助开发者设计出更加高效和可靠的实时系统。

本文将通过实际案例,详细介绍如何在实时 Linux 下实现数据流的实时传输,包括优化网络传输延迟的技术手段及方案。我们将从基本的网络传输概念入手,逐步深入到具体的实现细节和优化方法。

核心概念

1. 实时数据流

实时数据流是指需要在严格的时间约束内传输的数据序列。实时数据流的特性包括:

  • 时间敏感性:数据必须在规定的时间内到达目的地,否则可能失去价值。

  • 高吞吐量:数据流通常包含大量数据,需要高效的传输机制。

  • 可靠性:数据传输过程中需要保证数据的完整性和准确性。

2. 网络传输延迟

网络传输延迟是指数据从发送端到接收端所需的时间。延迟的来源包括:

  • 处理延迟:数据在发送端和接收端的处理时间。

  • 传输延迟:数据在物理介质中的传输时间。

  • 排队延迟:数据在网络节点(如路由器、交换机)中的排队时间。

  • 传播延迟:信号在物理介质中的传播时间。

3. 实时 Linux

实时 Linux 是一种经过优化的 Linux 系统,能够提供低延迟和高确定性的任务调度。它通过实时补丁(如 PREEMPT_RT)来增强 Linux 内核的实时性,适用于需要高实时性的应用场景。

4. 传输协议

传输协议是数据在网络中传输的规则。常见的传输协议包括:

  • TCP(传输控制协议):提供可靠的、面向连接的传输服务,适用于对可靠性要求较高的场景。

  • UDP(用户数据报协议):提供无连接的、不可靠的传输服务,适用于对实时性要求较高的场景。

  • RTP(实时传输协议):专门用于实时数据流的传输,支持数据的时间戳和序列号,适用于多媒体应用。

环境准备

1. 操作系统

  • 推荐系统:Ubuntu 20.04 或更高版本(建议使用实时内核,如 PREEMPT_RT)。

  • 安装实时内核

    1. 添加实时内核 PPA:

    2. sudo add-apt-repository ppa:longsleep/golang-backports
      sudo add-apt-repository ppa:ubuntu-toolchain-r/test
      sudo add-apt-repository ppa:realtime-linux/ppa
      sudo apt update
    3. 安装实时内核:

    4. sudo apt install linux-image-rt-amd64
    5. 重启系统并选择实时内核启动。

2. 开发工具

  • 推荐工具gcc(用于编译 C 程序)。

  • 安装方法

  • sudo apt update
    sudo apt install build-essential

3. 测试工具

  • 推荐工具iperf3(用于测试网络带宽和延迟)。

  • 安装方法

  • sudo apt install iperf3

实际案例与步骤

1. 使用 UDP 实现实时数据流传输

示例代码

以下代码展示了如何使用 UDP 实现实时数据流的传输。UDP 是一种无连接的协议,适用于对实时性要求较高的场景。

// sender.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in servaddr;// 创建 UDP 套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&servaddr, 0, sizeof(servaddr));// 配置服务器地址servaddr.sin_family = AF_INET;servaddr.sin_port = htons(PORT);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");char buffer[BUFFER_SIZE];while (1) {printf("Enter data to send: ");fgets(buffer, BUFFER_SIZE, stdin);// 发送数据sendto(sockfd, buffer, strlen(buffer), 0, (const struct sockaddr*)&servaddr, sizeof(servaddr));}close(sockfd);return 0;
}

 

// receiver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in servaddr, cliaddr;// 创建 UDP 套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&servaddr, 0, sizeof(servaddr));memset(&cliaddr, 0, sizeof(cliaddr));// 配置服务器地址servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(PORT);// 绑定套接字if (bind(sockfd, (const struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {perror("socket bind failed");exit(EXIT_FAILURE);}char buffer[BUFFER_SIZE];int n;socklen_t len = sizeof(cliaddr);while (1) {// 接收数据n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&cliaddr, &len);buffer[n] = '\0';printf("Received data: %s\n", buffer);}close(sockfd);return 0;
}
编译与运行
  1. 编译代码:

  2. gcc -o sender sender.c
    gcc -o receiver receiver.c
  3. 运行程序:

    • 在一个终端运行接收端:

  4. ./receiver
  5. 在另一个终端运行发送端:

  6. ./sender
    代码说明
    • UDP 套接字:使用 socket 函数创建 UDP 套接字。

    • 发送数据:使用 sendto 函数发送数据。

    • 接收数据:使用 recvfrom 函数接收数据。

    • 服务器地址:配置服务器的 IP 地址和端口号。

    2. 使用 TCP 实现实时数据流传输

    示例代码

    以下代码展示了如何使用 TCP 实现实时数据流的传输。TCP 是一种面向连接的协议,适用于对可靠性要求较高的场景。

    c

    复制

    // tcp_server.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>#define PORT 8080
    #define BUFFER_SIZE 1024int main() {int sockfd, connfd;struct sockaddr_in servaddr, cliaddr;// 创建 TCP 套接字if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&servaddr, 0, sizeof(servaddr));memset(&cliaddr, 0, sizeof(cliaddr));// 配置服务器地址servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(PORT);// 绑定套接字if (bind(sockfd, (const struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {perror("socket bind failed");exit(EXIT_FAILURE);}// 监听连接if (listen(sockfd, 5) < 0) {perror("socket listen failed");exit(EXIT_FAILURE);}int len = sizeof(cliaddr);if ((connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &len)) < 0) {perror("socket accept failed");exit(EXIT_FAILURE);}char buffer[BUFFER_SIZE];int n;while (1) {// 接收数据n = read(connfd, buffer, BUFFER_SIZE);buffer[n] = '\0';printf("Received data: %s\n", buffer);// 发送数据write(connfd, buffer, strlen(buffer));}close(sockfd);close(connfd);return 0;
    }

     

    // tcp_client.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>#define PORT 8080
    #define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in servaddr;// 创建 TCP 套接字if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&servaddr, 0, sizeof(servaddr));// 配置服务器地址servaddr.sin_family = AF_INET;servaddr.sin_port = htons(PORT);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 连接到服务器if (connect(sockfd, (const struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {perror("socket connection failed");exit(EXIT_FAILURE);}char buffer[BUFFER_SIZE];while (1) {printf("Enter data to send: ");fgets(buffer, BUFFER_SIZE, stdin);// 发送数据write(sockfd, buffer, strlen(buffer));// 接收数据read(sockfd, buffer, BUFFER_SIZE);printf("Received data: %s", buffer);}close(sockfd);return 0;
    }
    编译与运行
    1. 编译代码:

    2. gcc -o tcp_server tcp_server.c
      gcc -o tcp_client tcp_client.c
    3. 运行程序:

      • 在一个终端运行服务器端:

    4. ./tcp_server
    5. 在另一个终端运行客户端:

    6. ./tcp_client
      代码说明
      • TCP 套接字:使用 socket 函数创建 TCP 套接字。

      • 连接:使用 connect 函数连接到服务器。

      • 发送数据:使用 write 函数发送数据。

      • 接收数据:使用 read 函数接收数据。

      • 服务器地址:配置服务器的 IP 地址和端口号。

      3. 使用 RTP 实现实时数据流传输

      示例代码

      以下代码展示了如何使用 RTP 实现实时数据流的传输。RTP 是一种专门用于实时数据流的传输协议,支持数据的时间戳和序列号。

      // rtp_sender.c
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>
      #include <arpa/inet.h>#define PORT 8080
      #define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in servaddr;// 创建 UDP 套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&servaddr, 0, sizeof(servaddr));// 配置服务器地址servaddr.sin_family = AF_INET;servaddr.sin_port = htons(PORT);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");char buffer[BUFFER_SIZE];int seq_num = 0;while (1) {printf("Enter data to send: ");fgets(buffer, BUFFER_SIZE, stdin);// 添加 RTP 头部buffer[0] = (seq_num >> 8) & 0xFF; // 序列号高字节buffer[1] = seq_num & 0xFF;       // 序列号低字节seq_num++;// 发送数据sendto(sockfd, buffer, strlen(buffer) + 2, 0, (const struct sockaddr*)&servaddr, sizeof(servaddr));}close(sockfd);return 0;
      }

       

      // rtp_receiver.c
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>
      #include <arpa/inet.h>#define PORT 8080
      #define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in servaddr, cliaddr;// 创建 UDP 套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&servaddr, 0, sizeof(servaddr));memset(&cliaddr, 0, sizeof(cliaddr));// 配置服务器地址servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(PORT);// 绑定套接字if (bind(sockfd, (const struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {perror("socket bind failed");exit(EXIT_FAILURE);}char buffer[BUFFER_SIZE];int n;socklen_t len = sizeof(cliaddr);while (1) {// 接收数据n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&cliaddr, &len);buffer[n] = '\0';// 提取 RTP 头部int seq_num = (buffer[0] << 8) | buffer[1];printf("Received data (seq_num: %d): %s\n", seq_num, buffer + 2);}close(sockfd);return 0;
      }
      编译与运行
      1. 编译代码:

      2. gcc -o rtp_sender rtp_sender.c
        gcc -o rtp_receiver rtp_receiver.c
      3. 运行程序:

        • 在一个终端运行接收端:

      4. ./rtp_receiver
      5. 在另一个终端运行发送端:

      6. ./rtp_sender
        代码说明
        • RTP 头部:在数据前添加序列号,用于标识数据包的顺序。

        • UDP 套接字:使用 socket 函数创建 UDP 套接字。

        • 发送数据:使用 sendto 函数发送数据。

        • 接收数据:使用 recvfrom 函数接收数据。

        • 服务器地址:配置服务器的 IP 地址和端口号。

        常见问题与解答

        1. 如何选择合适的传输协议?

        选择合适的传输协议取决于具体的应用场景:

        • UDP:适用于对实时性要求较高的场景,如视频流、音频流。

        • TCP:适用于对可靠性要求较高的场景,如文件传输、数据库通信。

        • RTP:适用于需要时间戳和序列号的实时数据流,如多媒体应用。

        2. 如何优化网络传输延迟?

        可以通过以下方法优化网络传输延迟:

        • 使用 UDP:UDP 是一种无连接的协议,传输延迟较低。

        • 减少数据包大小:较小的数据包可以减少传输延迟。

        • 使用实时 Linux:实时 Linux 可以提供低延迟和高确定性的任务调度。

        • 优化网络配置:调整网络参数,如 MTU(最大传输单元)和缓冲区大小。

        3. 如何调试网络传输问题?

        可以通过以下方法调试网络传输问题:

        • 使用 iperf3:测试网络带宽和延迟。

        • 使用 tcpdump:捕获和分析网络数据包。

        • 使用 netstat:查看网络连接和端口状态。

        4. 如何处理数据包丢失?

        可以通过以下方法处理数据包丢失:

        • 使用 TCP:TCP 提供可靠的数据传输,可以自动处理数据包丢失。

        • 实现重传机制:在 UDP 中实现自定义的重传机制。

        • 使用 FEC(前向纠错):在数据中添加冗余信息,以便在数据包丢失时恢复数据。

        实践建议与最佳实践

        1. 合理选择传输协议

        根据具体的应用场景选择合适的传输协议,避免过度优化导致复杂性增加。

        2. 使用实时 Linux

        实时 Linux 可以提供低延迟和高确定性的任务调度,适用于需要高实时性的应用场景。

        3. 优化网络配置

        调整网络参数,如 MTU 和缓冲区大小,以减少传输延迟。

        4. 使用调试工具

        在开发过程中,使用调试工具(如 iperf3tcpdumpnetstat)可以帮助你更好地理解和解决网络传输问题。

        5. 实现重传机制

        在 UDP 中实现自定义的重传机制,以处理数据包丢失。

        6. 使用 FEC

        在数据中添加冗余信息,以便在数据包丢失时恢复数据。

        总结与应用场景

        本文通过实际案例,详细介绍了如何在实时 Linux 下实现数据流的实时传输,包括优化网络传输延迟的技术手段及方案。实时数据流的传输在许多领域都有广泛的应用,如工业自动化、金融交易、多媒体应用等。希望读者能够将所学知识应用到真实项目中,优化系统的实时性能。如果你有任何问题或建议,欢迎在评论区留言。

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

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

        相关文章

        C#数组(一维数组、多维数组、交错数组、参数数组)

        在 C# 中&#xff0c;数组是一种用于存储固定大小的相同类型元素的集合。数组可以包含值类型、引用类型或对象类型的元素&#xff0c;并且在内存中是连续存储的。以下是关于 C# 数组的详细介绍&#xff1a;1. 一维数组声明与初始化// 声明数组 int[] numbers; // 声…

        Dify离线安装包-集成全部插件、模板和依赖组件,方便安可内网使用

        项目介绍 Dify一键离线安装包&#xff0c;集成安装了全部插件、模板&#xff0c;并集成了dify全部插件所需的依赖组件。方便你在内网、安可环境等离线状态下使用。 Dify是一个开源的LLM应用开发平台。其直观的界面结合了AI工作流、RAG管道、Agent、模型管理、可观测性功能等&…

        面试150 翻转二叉树

        思路 采用先序遍历&#xff0c;可以通过新建根节点node&#xff0c;将原来root的右子树连到去node的左子树中&#xff0c;root的左子树连到去node的右子树中。 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): …

        C++-linux系统编程 3.gcc编译工具

        GCC编译工具链完全指南 GCC&#xff08;GNU Compiler Collection&#xff09;是Linux系统下最常用的编译器套件&#xff0c;支持C、C、Objective-C等多种编程语言。本章将深入讲解GCC的编译流程、常用选项及项目实战技巧。 一、GCC编译的四个核心阶段 GCC编译一个程序需要经过四…

        uView UI 组件大全

        uView UI 是一个基于 uni-app 的高质量 UI 组件库&#xff0c;提供丰富的跨平台组件&#xff08;支持 H5、小程序、App 等&#xff09;。以下是其核心组件的分类大全及功能说明&#xff0c;结合最新版本&#xff08;1.2.10&#xff09;整理&#xff1a; &#x1f4e6; 一、基础…

        QWidget 和 QML 的本质和使用上的区别

        QWidget 和 QML 是 Qt 框架中两种不同的 UI 开发技术&#xff0c;它们在底层实现、设计理念和使用场景上有显著区别。以下是它们的本质和主要差异&#xff1a;1. 本质区别特性QWidgetQML (Qt Modeling Language)技术基础基于 C 的面向对象控件库基于声明式语言&#xff08;类似…

        中转模型服务的风险

        最近发现一些 AI 相关帖子下&#xff0c;存在低质 claude code 中转的小广告。 其中转的基本原理就是 claude code 允许自己提供 API endpoint 和 key&#xff0c;可以使用任意一个 OpenAI API 兼容的供应商&#xff0c;就这么简单。 进一点 claude token&#xff0c;再混入一点…

        前端Vue.js面试题(3)

        ✨✨✨目录 1.v-model的原理是什么样的&#xff1f; 2.Vue的生命周期&#xff1f; 3.Vue子组件和父组件执行顺序&#xff1f; 4.created和mounted的区别&#xff1f; 5.vue中&#xff0c;推荐在哪个生命周期发起请求&#xff1f; 6.keep-alive中的生命周期有哪些&#xf…

        leetcode:HJ18 识别有效的IP地址和掩码并进行分类统计[华为机考][字符串]

        学习要点 bitset<8>ostringstreamstoistring.findstring.substr 题目链接 识别有效的IP地址和掩码并进行分类统计_牛客题霸_牛客网 题目描述 解法 #include <iostream> #include <bits/stdc.h> #include <sstream> #include <string> #inclu…

        JavaEE Tomcat

        企业开发介绍 JavaEE 规范 JavaEE规范是J2EE规范的新名称,早期被称为 J2EE 规范,其全称是 Java 2 Platform Enterprise Edition,是由 SUN 公司领导、各厂家共同制定并得到广泛认可的工业标准(JCP 组织成员)。 其中,JCP 组织(官网)的全称是 Java Community Process,…

        什么是神经网络,常用的神经网络,如何训练一个神经网络

        神经网络&#xff1a;是深度学习的核心技术。模仿生物神经元工作方式的计算模型&#xff0c;由大量互相连接是神经元组成&#xff0c;通过数据学习复杂的模式和关系。1、神经网络基本组成&#xff1a;神经元、层、连接神经元神经网络的最小单元。每个神经元接受输入&#xff0c…

        BigFoot Decursive 2.7.28 2025.07.11

        插件显示为独立插件&#xff0c;之前是团队框架自带 BigFoot Decursive lua-CSDN博客 /decursive 命令打开插件 /DCRSHOW 打开设置列表 然后优先列表里面再点【p】添加&#xff0c;你要驱散得优先职业 一键驱散lua插件下载&#xff1a; https://download.csdn.net/downloa…

        可穿戴智能硬件在国家安全领域的应用

        可穿戴智能硬件在国家安全领域具有广泛应用&#xff0c;涵盖军事作战、安防监控、边境巡逻等多个方面&#xff0c;以下是具体介绍&#xff1a;军事作战与训练&#xff1a;战场态势感知&#xff1a;士兵佩戴集成多种传感器的智能头盔、智能背心等&#xff0c;可实时获取战场环境…

        后端接口通用返回格式与异常处理实现

        前言 目前大部分系统都是前后端分离架构&#xff0c;后端提供接口并返回 JSON 数据&#xff0c;前端接收数据后进行处理展示。为了提高前后端协作效率&#xff0c;后端接口返回值采用固定格式十分必要。 后端接口返回值通用格式 通用返回值通常包含 4 个核心字段&#xff0c…

        【yolo】模型训练参数解读

        在YOLO&#xff08;You Only Look Once&#xff09;目标检测模型的训练过程中&#xff0c;数据增强是一项至关重要且极具“艺术性”的技术。它通过对训练图像进行一系列随机变换&#xff0c;人为地创造出更多样化的训练样本&#xff0c;从而有效提升模型的泛化能力、鲁棒性&…

        IPsec:网络层的加密盾牌与HTTPS的差异解析

        ​​一、IPsec核心原理​​1. 安全封装结构​┌───────────────┬────────────────┬──────────────────────┐ │ IP头部 │ IPSec头部 │ 加密/认证的载荷 │ │ (路由寻址) │ (AH/ESP) │…

        【Python办公】Python如何批量提取PDF中的表格

        目录 专栏导读概述主要工具库介绍1. tabula-py2. camelot-py3. pdfplumber4. PyMuPDF (fitz)环境准备安装依赖Java环境配置(tabula-py需要)方法一:使用tabula-py提取表格基础用法高级配置方法二:使用camelot-py提取表格方法三:使用pdfplumber提取表格批量处理多个PDF文件数…

        MySQL自定义order by排序规则

        数据表create table tb_user (id bigint auto_incrementprimary key,name varchar(16) not null,age int not null,address varchar(128) null );INSERT INTO test.tb_user (id, name, age, address) VALUES (1, 张三, 18, China); INSERT INTO test.tb_…

        112套开题答辩行业PPT模版

        毕业答辩开题报告&#xff0c;毕业答辩&#xff0c;论文设计PPT&#xff0c;清新论文答辩PPT模版&#xff0c;毕业论文答辩开题报告PPT&#xff0c;答辩演讲通用PPT模版&#xff0c;文艺时尚毕业答辩PPT模版&#xff0c;简约毕业论文答辩PPT模版112套开题答辩行业PPT模版&#…

        驱动开发系列61- Vulkan 驱动实现-SPIRV到HW指令的实现过程(2)

        本节继续介绍下SPIR-V到LLVM IR的转换过程,重点分析其核心机制和关键转换步骤。我们将从 LLVM 入手,结合实SPIR-V结构逐步转换为符合 LLVM IR 语义的表示方式。 一:详细过程 1. 创建llvm::module llvm::LLVMContext llvmContext; std::unique_ptr<llvm::Mod…