在 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);
        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年

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值