在 uboot 中实现 UDP 协议

news2025/1/12 7:02:15

一、明确背景

        uboot中有许多通信协议,像TFTP、NFS等,这些协议底层都是基于UDP协议来实现的,由于有一个板子在 uboot 段进行固件下载更新的需求,本来想基于TCP协议来实现自定义通信协议(TCP有自带的拥塞控制和重传机制)。但是,uboot底层并不支持TCP协议栈,且TCP协议栈的默认超时重传时间过长,移植TCP协议栈到uboot中难度也是相当大。最后结合需求考虑下来,决定基于UDP协议来实现自定义通信协议。第一步,要先验证UDP协议能否走通(不用想大概率是可以的,因为uboot中支持的其他协议底层也是基于UDP的,但还是走一遍验证一下,也是为后面的自定义通信协议打好基础)

        抓住一个思想:TFTP底层就是基于UDP的,有什么不清楚的地方就看uboot中的TFTP源码,往往会指出些方向。

二、代码实现

        首先在common/cmd_net.c中添加如下代码,这一步的目的是在uboot的命令中添加一个名为udp的命令,当我们在控制台输入udp后便会执行do_udp函数,然后逐层往下调用其他函数

/*my UDP*/
int do_udp(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
        int ret;

        ret = netboot_common_udp(UDP, cmdtp, argc, argv);
        return ret;
}

U_BOOT_CMD(
        udp,    6,      1,      do_udp,
        "send or recv message to from server using UDP protocol",
        "[udp]"
);

接着继续在该文件中定义这样一个函数,该函数是用来处理uboot控制台输入的具体命令参数的

/*my netboot_common*/
static int netboot_common_udp(enum proto_t proto, cmd_tbl_t *cmdtp, int argc, char *argv[])
{
        int   rcode = 0;
        int   size;

        switch (argc)
        {
        case 1:
                break;
        case 2:
                pkt_data = argv[1];
                break;
        default:
                bootstage_error(BOOTSTAGE_ID_NET_START);
                return CMD_RET_USAGE;
        }

        bootstage_mark(BOOTSTAGE_ID_NET_START);
        if ((size = NetLoop(proto)) < 0) {
                bootstage_error(BOOTSTAGE_ID_NET_NETLOOP_OK);
                return 1;
        }
        bootstage_mark(BOOTSTAGE_ID_NET_NETLOOP_OK);

        /* NetLoop ok, update environment */
        netboot_update_env();

        /* done if no file was loaded (no errors though) */
        if (size == 0) {
                bootstage_error(BOOTSTAGE_ID_NET_LOADED);
                return 0;
        }

        
        if (rcode < 0)
                bootstage_error(BOOTSTAGE_ID_NET_DONE_ERR);
        else
                bootstage_mark(BOOTSTAGE_ID_NET_DONE);
        return rcode;
}

这个函数调用了net/net.c中的net_loop()函数,到这里本路径下的修改工作已完成。跳转到net/net.c中,在netloop函数的switch中添加case:UDP

#ifdef CONFIG_CMD_TFTPSRV
                case TFTPSRV:
                        TftpStartServer();
                        break;
#endif
                case UDP:
                        UdpStart();            //my udp
                        break;

#if defined(CONFIG_CMD_DHCP)
                case DHCP:
                        BootpReset();
                        NetOurIP = 0;
                        DhcpRequest();          /* Basically same as BOOTP */
                        break;
#endif

还需要在include/net.h中添加UDP协议的声明

注意在net.h中要声明该变量是定义在别处的,这样编译器就能知道pkt_data(用来接终端输入的待传数据的)是一个在其他地方定义的全局变量,从而允许在common.c中正确地访问和使用这个变量。

至此UDP协议支持的相关配置工作已经完成,接下来要自己在net目录下写一个UDP协议的服务函数udp.c和udp.h

#include <common.h>
#include <command.h>
#include <net.h>
#include "tftp.h"
#include "bootp.h"
#include <flash.h>
#include "udp.h"


uchar *pkt_data;
static int UdpPktLen;


static IPaddr_t UdpServerIP;
static int      UdpServerPort;          /* The UDP port at their end            */
static int      UdpOurPort;             /* The UDP port at our end              */
static int      UdpTimeoutCount;

static void UdpSend (void);


/**********************************************************************/

static void UdpSend (void)
{
        uchar *pkt;           //指向待发送数据包的指针
        int len = 0;          //数据包长度

        pkt = NetTxPacket + NetEthHdrSize() + IP_UDP_HDR_SIZE;
//      printf("NetEthHdrSize() = %d\n", NetEthHdrSize());
//      printf("IP_UDP_HDR_SIZE = %d\n", IP_UDP_HDR_SIZE);
        len = strlen(pkt_data);
        memcpy(pkt, pkt_data, len);
        printf("pkt_data = %s,len = %d\n", pkt_data, len);
//      printf("pkt = %s\n", pkt);
        NetSendUDPPacket(NetServerEther, UdpServerIP, UdpServerPort, UdpOurPort, len);
}

static void UdpHandler (uchar *pkt, unsigned dest, IPaddr_t UdpServerIP, unsigned src, unsigned len)
{
        printf("---- receive udp packet ----\n");
        printf("len = %d\n",len);
        printf("data = %s\n", pkt);

        UdpSendAck();
}

void UdpStart (void)
{
        char *ep;             /* Environment pointer */

        if ((ep = getenv("serverip")) != NULL)
        {
                printf("ep = %s\n", ep);
                UdpServerIP = string_to_ip((const char *)ep);
        }

#if defined(CONFIG_NET_MULTI)
        printf ("Using %s device\n", eth_get_name());
#endif
        
        net_set_udp_handler(UdpHandler);        // 设置UDP数据包处理函数为UdpHandler

        UdpServerPort = 50000;      //主机端口号;
        UdpOurPort = 30000;
       
        memset(NetServerEther, 0, 6);   //如果服务器IP地址发生了变化,清零服务器以太网地址(MAC地址)

        UdpSend ();
}
#ifndef __UDP_H__
#define __UDP_H__

extern void UdpStart (void);

#endif

别忘了在net目录下的Makefile中添加编译依赖项,保证udp.c被编译进镜像文件

至此uboot源码中的相关工作已经完成,接下来进行调试验证

写两个上位机程序来进行数据的收和发

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void)
{
        int sockfd = 0;
        char tmpbuff[1024] = {0};
        struct sockaddr_in recvaddr;
        size_t nsize = 0;
        int ret = 0;

        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (-1 == sockfd)
        {
                perror("fail to socket\n");
                return -1;
        }

        recvaddr.sin_family = AF_INET;
        recvaddr.sin_port = htons(50000);
        recvaddr.sin_addr.s_addr = INADDR_ANY;
        ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
        if (ret == -1)
        {
                perror("fail to bind");
                return -1;
        }

        nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
        if (nsize == -1)
        {
                perror("fail to recvfrom");
                return -1;
        }

        printf("接收  %ld 个字节\n", nsize);
        printf("Recv: %s\n", tmpbuff);

        close(sockfd);
}

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void)
{
        int sockfd = 0;
        char tmpbuff[1024] = {0};
        struct sockaddr_in recvaddr;
        size_t nsize = 0;

        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (-1 == sockfd)
        {
                perror("fail to socket\n");
                return -1;
        }

        gets(tmpbuff, strlen(tmpbuff), stdin);
        recvaddr.sin_family = AF_INET;
        recvaddr.sin_port = htons(30000);
        recvaddr.sin_addr.s_addr = inet_addr("172.31.13.207");

        nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
        if (nsize == -1)
        {
                perror("fail to sendto");
                return -1;
        }

        printf("发送 %ld 个字节\n", nsize);

        close(sockfd);
}

三、测试

在uboot中发,上位机来收

在上位机中发,uboot中来收

极限性能测试

测试验证发发现,在不经过网络转发设备的前提下能跑满UDP协议理论最大包长1472字节。

四、补充说明

        以上只是简单在uboot中走通了UDP协议,故代码中不涉及待传数据的帧格式设计、重传机制、超时机制等,若要实现基于UDP协议自定义通信协议,还需要设计好以上几点。这个后面再说。

        注:uboot版本为2017年

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

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

相关文章

Microsoft Visual C++ Redistributable的作用主要体现以及可以删除吗?

这些是Microsoft Visual C的不同版本的Redistributable&#xff08;可再发行组件包&#xff09;安装包&#xff0c;用于在用户的计算机上安装或更新必要的运行时库&#xff0c;以便运行使用这些版本的Visual C开发的应用程序。具体来说&#xff1a; Microsoft Visual C 2012 R…

编译linux内核时,让版本号不跟着git变化

文章目录 编译linux内核时&#xff0c;让版本号不跟着git变化现象方法一方法二 编译linux内核时&#xff0c;让版本号不跟着git变化 现象 内核每次重新编译时&#xff0c;uname -r都会跟着变。 4.1.15-00005-g482731e4-dirty 导致报错&#xff0c;modprobe: can’t change …

《深入理解Java虚拟机(第2版)》- 第1章 - 学习笔记

1.1 概述 Java不止是一门编程语言&#xff0c;它是一些列计算机软件和规范所形成的技术体系。 1.2 技术体系 Sun官方定义Java技术体系由5各部分组成&#xff0c;如下&#xff1a; Java编程语言JVMClass文件格式Java API 类库第三方Java类库&#xff08;商业或开源&#xff…

养猫家庭必备好物——宠物空气净化器,让浮毛无处可逃

前不久&#xff0c;我爸妈担心我独自一个人来到一个陌生的城市打拼&#xff0c;身边没人陪我会孤单&#xff0c;但其实我一点儿都不孤单。虽然我家里没有人陪我聊天说话一起做饭&#xff0c;但是有猫陪我。 之前来到这座城市的第二年就养了猫&#xff0c;心情确实好很多&#…

【数据分享】《中国奶业年鉴》2002-2020(缺2014)

而今天要限时免费分享的数据就是2002-2020年间出版的《中国奶业年鉴》并以多格式提供免费下载。&#xff08;无需分享朋友圈即可获取&#xff09; 数据介绍 《中国奶业年鉴》是反映我国奶业发展情况的综合性年刊&#xff0c;也是农业农村部&#xff08;原农业部&#xff0…

13、java 数组常见算法(经典案例)、Arrays类的使用(操作数组工具类)、数组常见异常(角标越界或空指针)

java 数组 Ⅰ、数组常见算法&#xff1a;1、二维数组的案例1&#xff1a;求和其一、代码为&#xff1a;其二、截图为&#xff1a; 2、二维数组的案例2&#xff1a;杨辉三角其一、描述&#xff1a;其二、代码为&#xff1a;其三、截图为&#xff1a; 3、二维数组的案例3&#xf…

如何使用jd-gui对springboot源码进行分析

背景&#xff1a; 最近在学习springboot的过滤器和拦截器&#xff0c;想了解一下过滤器和拦截器是怎么匹配URL的&#xff0c;在网上搜了半天都搜不到针对源码的&#xff0c;网上大部分内容都是说怎么配置过滤器和拦截器&#xff0c;怎么使用&#xff0c;并没有对源码进行分析的…

10分钟出稿!分享最佳6款AI智能一键生成毕业论文

在当前的学术环境中&#xff0c;AI智能写作工具已经成为许多学生和研究人员的重要助手。这些工具不仅能够帮助快速生成论文初稿&#xff0c;还能提供文献综述、语法检查等多种功能&#xff0c;极大地简化了学术写作流程。以下是六款最佳AI智能一键生成毕业论文的推荐&#xff1…

大学生实用工具!分享5款靠谱AI一键生成毕业论文的网站

对于大学生来说&#xff0c;毕业论文是一项重要的学术任务&#xff0c;但往往也是最令人头疼的部分。幸运的是&#xff0c;随着人工智能技术的发展&#xff0c;现在有一些工具可以帮助学生轻松完成论文。以下是五款靠谱的AI一键生成毕业论文的网站&#xff0c;其中特别推荐千笔…

EXCEL格式转化

1.小数位的保留 ROUND 四舍五入&#xff0c; ROUNDUP向上进一位 ROUNDDOWN向下进一位 2.货币符号与单位的添加 CTRLSHIFT4自定义格式&#xff0c;只改变显示&#xff0c;不改变数值 DOLLAR,RMB 3.英文大小写转化 UPPER小写变大写 LOWER大写变小写 PROPER首字母大写 …

ArcGIS JSAPI 高级教程 - ArcGIS Maps SDK for JavaScript - 自定义范围后处理效果(优化版)

ArcGIS JSAPI 高级教程 - ArcGIS Maps SDK for JavaScript - 自定义范围后处理效果&#xff08;优化版&#xff09; 核心代码完整代码在线示例 ArcGIS Maps SDK for JavaScript 从 4.29 开始增加 RenderNode 类&#xff0c;可以添加数据以及操作 FBO&#xff08;ManagedFBO&…

微信公众号扫码登录

开发前准备 微信公众平台微信公众平台&#xff0c;给个人、企业和组织提供业务服务与用户管理能力的全新服务平台。https://mp.weixin.qq.com/申请注册个服务号&#xff0c;并且需要微信认证&#xff0c;注意个人公众号无法开通这个功能&#xff0c;因为个人就不给做微信认证。…

Python | Leetcode Python题解之第350题两个数组的交集II

题目&#xff1a; 题解&#xff1a; class Solution:def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:nums1.sort()nums2.sort()length1, length2 len(nums1), len(nums2)intersection list()index1 index2 0while index1 < length1 and ind…

LLM 中 100K 上下文窗口背后的秘密

最近有几则关于新型大型语言模型 (LLM) 的公告&#xff0c;这些模型可以使用极大的上下文窗口&#xff0c;例如65K 个标记&#xff08;MosaicML 的MPT-7B-StoryWriter-65k &#xff09;甚至100K 个标记&#xff08; Antropic 的引入 100K 上下文窗口&#xff09;。在 Palm-2技术…

C++ | Leetcode C++题解之第354题俄罗斯套娃信封问题

题目&#xff1a; 题解&#xff1a; class Solution { public:int maxEnvelopes(vector<vector<int>>& envelopes) {if (envelopes.empty()) {return 0;}int n envelopes.size();sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const aut…

一招制胜!Spring Boot、Prometheus和Grafana三剑合璧,称霸监控领域!

1. 添加Prometheus和Actuator依赖 在pom.xml中添加Spring Boot Actuator和Micrometer Prometheus依赖&#xff1a; <dependencies> <!--监控功能Actuator--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring…

光伏电站气象站:提升电站效益,智能监控

随着全球对可持续发展和清洁能源需求的日益增长&#xff0c;光伏发电作为一种清洁、可再生的能源形式&#xff0c;正逐步成为能源结构转型的重要支柱。然而&#xff0c;光伏系统的发电效率直接受到气候条件的影响&#xff0c;如光照强度、温度、湿度、风速及风向等。因此&#…

网上商城购物系统

TOC springboot0752网上商城购物系统 第1章 绪论 1.1背景及意义 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们生活水平的不断提高&#xff0c;日常生活中人们对网上商城购物系统方面的要求也在不断提高&#xff0c;购物的人数更是不断增加&#xff0c;…

Kubernetes--深入Pod

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 出自B站博主教程笔记&#xff1a; ​​​​​​​完整版Kubernetes&#xff08;K8S&#xff09;全套入门微服务实战项目&#xff0c;带你一站式深入…

什么是BOM,有哪些分类?

一、什么是BOM&#xff1f; BOM是物料清单的缩写&#xff0c;也称为产品结构表或产品结构树。 BOM的作用主要是通过计算机辅助企业生产管理&#xff0c;使计算机能够识别企业所制造的产品构成和所有要涉及的物料。 在制造业中&#xff0c;BOM是一份详细记录制造某个产品时所…