在 uboot 中实现 UDP 协议

一、明确背景

        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);elsebootstage_mark(BOOTSTAGE_ID_NET_DONE);return rcode;
}

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

#ifdef CONFIG_CMD_TFTPSRVcase TFTPSRV:TftpStartServer();break;
#endifcase UDP:UdpStart();            //my udpbreak;#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());
#endifnet_set_udp_handler(UdpHandler);        // 设置UDP数据包处理函数为UdpHandlerUdpServerPort = 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.mzph.cn/diannao/52194.shtml

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

相关文章

vue生命周期函数

文章目录 1.vue21.1 Vue 生命周期 和 生命周期的四个阶段1.2 Vue 生命周期函数&#xff08;钩子函数&#xff09; 2.vue3 1.vue2 1.1 Vue 生命周期 和 生命周期的四个阶段 &#xff08;1&#xff09;create指的是数据代理和数据监测的创建 1.2 Vue 生命周期函数&#xff08;钩…

docker手动部署django项目Dockerfile编排-后端发布

1、首先创建一个桥接网络 docker network create auto 2、部署redis,提供celery的消息队列服务 docker run --name redis --restartalways -d --network auto -v redis:/data redis:alpine3、部署数据库 注意数据库账号密码 docker run --name mariadb --restartalways -d…

九泰智库 | 医械周刊- Vol.51

⚖️ 法规动态 白内障人工晶体类医用耗材集采落地&#xff0c;平均降幅60% 湖北省自7月10日起实施人工晶体类医用耗材集中带量采购政策&#xff0c;中选产品平均降幅达60%&#xff0c;显著减轻了患者经济负担。此举是国家组织医用耗材采购的一部分&#xff0c;旨在通过集中采购…

完美解决html2canvas + jsPDF导出pdf分页内容截断问题

代码地址&#xff1a;https://github.com/HFQ12333/export-pdf.git html2canvas jspdf方案是前端实现页面打印的一种常用方案&#xff0c;但是在实践过程中&#xff0c;遇到的最大问题就是分页截断的问题&#xff1a;当页面元素超过一页A4纸的时候&#xff0c;连续的页面就会…

c++ 谷歌的招聘 题解

题目描述 2004 年 7 月,谷歌在硅谷的 101 号公路边竖立了一块巨大的广告牌(如下图)用于招聘 内容超级简单,就是一个以 .com 结尾的网址,而前面的网址是一个 10 位素数,这个素数是自然常数 e 中最早出现的 10 位连续数字 能找出这个素数的人,就可以通过访问谷歌的这个网站进入…

CSS”叠叠乐“——WEB开发系列16

在现代前端开发中&#xff0c;CSS 是控制网页外观和布局的核心工具。随着项目的复杂化和样式规则的增加&#xff0c;CSS 层叠&#xff08;cascade&#xff09;变得更加重要。为了更好地管理和控制样式规则的应用&#xff0c;CSS 引入了层叠层&#xff08;cascade layers&#x…

Java—Lambda表达式

注意&#xff1a;如果无法判断一个方法是否为函数式接口&#xff0c;可以查看该方法的源码中是否携带FunctionalInterface注解。 lambda表达式再简化写法规则如下。 1. 参数类型可以省略不写 2. 如果只有一个参数&#xff0c;参数的 "( )" 也可以省略。 3. 如果Lambd…

【PyTorch】神经网络的基本骨架-nn.Module的使用以及convolution-layers卷积层介绍

前提文章目录 【PyTorch】深度学习PyTorch环境配置及安装【详细清晰】 【PyTorch】深度学习PyTorch加载数据 【PyTorch】关于Tensorboard的简单使用 【PyTorch】关于Transforms的简单使用 【PyTorch】关于torchvision中的数据集以及dataloader的使用 文章目录 前提文章目录nn.…

AI编程工具的力量:以AWS Toolkit与百度Comate为例,加速程序员开发效率

在当今的数字化转型浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技术不仅重塑了众多行业&#xff0c;也为软件开发领域带来了革命性的变化。AI编程工具&#xff0c;凭借其智能化的特性&#xff0c;正在成为程序员提高开发效率、优化代码质量和加速产品迭代的重要助力…

SQL注入(原理、分类、union、POST注入)

目录 【学习目标、重难点知识】 【学习目标】 【重难点知识】 SQL注入简介 SQL注入原理 SQL注入类型 MySQL与SQL注入的相关知识 information_schema 数据库的结构 数据库查询语句 limit的用法 需要记住的几个函数 注释符号 SQL注入探测方法 SQL注入漏洞攻击流程…

以简单的例子从头开始建spring boot web多模块项目(五)-thymeleaf引擎

继续向里面加&#xff0c;这次是引入thymeleaf渲染引擎。 使用这个引擎的很多&#xff0c;主要是以下几个优点&#xff1a; Thymeleaf是适用于Web和独立环境的现代服务器端Java模板引擎。Thymeleaf的主要目标是为您的开发工作流程带来优雅的自然模板 -HTML可以在浏览器中正确显…

七牛云 CDN 视频瘦身,为视频分发「减负增效」

随着智能设备的普及&#xff0c;以及各种以分享视频为主的平台的兴起&#xff0c;人们记录生活、分享故事的方式不再局限于文字和图片&#xff0c;而是越来越多地通过视频来表达。视频也不再需要复杂的制作过程&#xff0c;变得随手可得。 然而&#xff0c;视频在互联网上的爆炸…

65 华为交换机CE6800基础配置

一 IPV6 使能配置思路 1 改华为交换机的名字 <huaweice65>sys Enter system view, return user view with return command. [~huaweice65]sysname HWCE6800 [*huaweice65]quit Warning: Uncommitted configurations found. Are you sure to commit them before exiting?…

抖音集团 FlinkSQL 性能优化探索及实践

本文作者&#xff1a;李精卫 更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 背景 随着抖音集团内部对流式任务的需求不断增长&#xff0c;Flink SQL作为一种低成本接入手段&#xff0c;已经在内部多个方向上得到…

K8S系列——(二)、K8S部署RocketMQ集群

1、环境准备 要将RocketMQ部署到K8S上&#xff0c;首先你需要提前准备一个K8S集群环境&#xff0c;如图我已经准备好了一个版本为 v1.28.13 的 K8S 集群&#xff08;其他版本也没问题&#xff09;&#xff1a; 角色IPMaster192.168.6.220Node-1192.168.6.221Node-2192.168.6.…

React 学习——Class类组件的基本结构

老版本的react的写法&#xff1a;是通过class类组件的 import { Component } from react; class Counter extends Component{state {count: 0,}setCount ()>{this.setState({ count: this.state.count 1 })}render(){return <button onClick{this.setCount}>{thi…

【Qt】输入类控件QLineEdit

目录 输入类控件QLineEdit 例子&#xff1a;录入个人信息 例子&#xff1a;使用正则表达式验证输入框的数据 例子&#xff1a;验证俩次输入密码一致 例子&#xff1a;切换显示代码 输入类控件QLineEdit QLineEdit 用来表示单行输入框&#xff0c;可以输入一段文本&#xf…

Go 文件操作基本方法大全

前言 在Go语言中&#xff0c;操作文件主要依赖于标准库中的os和io/ioutil&#xff08;注意&#xff1a;io/ioutil在Go 1.16及以后版本中被逐步弃用&#xff0c;推荐使用io和os包中的函数进行替代&#xff09;以及io和bufio等包。以下是一些基于这些基本库操作文件的方法大全&a…

Python和MATLAB谐波生成导图

&#x1f3af;要点 绘制三次谐波生成透射功率谱、对数对数图表示半导体曲面二次谐波生成&#xff0c;分析判断材料特性谐波均值估计计算边际似然&#xff08;贝叶斯统计&#xff09;二次谐波散射分析胶体染料分子结构交流电谐波波形傅立叶分析分析旋转各向异性谐波高次谐波非线…

appium下载及安装

下载地址&#xff1a;https://github.com/appium/appium-desktop/releases 双击安装就可以